diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8859205..4426f42 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ xmlns:tools="http://schemas.android.com/tools"> + Snackbar(snackbarData, contentColor = MaterialTheme.colors.onPrimary) + } + ) + }, + scaffoldState = appState.scaffoldState + ) { innerPaddingModifier -> + NavHost( + navController = appState.navController, + startDestination = StudeezDestinations.SPLASH_SCREEN, + modifier = Modifier.padding(innerPaddingModifier) + ) { + studeezGraph(appState) + } + } + } + } +} + +@Composable +fun rememberAppState( + scaffoldState: ScaffoldState = rememberScaffoldState(), + navController: NavHostController = rememberNavController(), + snackbarManager: SnackbarManager = SnackbarManager, + resources: Resources = resources(), + coroutineScope: CoroutineScope = rememberCoroutineScope() +) = + remember(scaffoldState, navController, snackbarManager, resources, coroutineScope) { + StudeezAppstate(scaffoldState, navController, snackbarManager, resources, coroutineScope) + } + +@Composable +@ReadOnlyComposable +fun resources(): Resources { + LocalConfiguration.current + return LocalContext.current.resources +} + +fun NavGraphBuilder.studeezGraph(appState: StudeezAppstate) { + + composable(StudeezDestinations.SPLASH_SCREEN) { + SplashScreen(openAndPopUp = { route, popUp -> appState.navigateAndPopUp(route, popUp) }) + } + + +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/StudeezAppstate.kt b/app/src/main/java/be/ugent/sel/studeez/StudeezAppstate.kt new file mode 100644 index 0000000..cc28cd3 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/StudeezAppstate.kt @@ -0,0 +1,51 @@ +package be.ugent.sel.studeez + +import android.content.res.Resources +import androidx.compose.material.ScaffoldState +import androidx.compose.runtime.Stable +import androidx.navigation.NavHostController +import be.ugent.sel.studeez.common.snackbar.SnackbarManager +import be.ugent.sel.studeez.common.snackbar.SnackbarMessage.Companion.toMessage +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.launch + +@Stable +class StudeezAppstate( + val scaffoldState: ScaffoldState, + val navController: NavHostController, + private val snackbarManager: SnackbarManager, + private val resources: Resources, + coroutineScope: CoroutineScope +) { + init { + coroutineScope.launch { + snackbarManager.snackbarMessages.filterNotNull().collect { snackbarMessage -> + val text = snackbarMessage.toMessage(resources) + scaffoldState.snackbarHostState.showSnackbar(text) + } + } + } + + fun popUp() { + navController.popBackStack() + } + + fun navigate(route: String) { + navController.navigate(route) { launchSingleTop = true } + } + + fun navigateAndPopUp(route: String, popUp: String) { + navController.navigate(route) { + launchSingleTop = true + popUpTo(popUp) { inclusive = true } + } + } + + fun clearAndNavigate(route: String) { + navController.navigate(route) { + launchSingleTop = true + popUpTo(0) { inclusive = true } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/StudeezHiltApp.kt b/app/src/main/java/be/ugent/sel/studeez/StudeezHiltApp.kt new file mode 100644 index 0000000..5db407f --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/StudeezHiltApp.kt @@ -0,0 +1,7 @@ +package be.ugent.sel.studeez + +import android.app.Application +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class StudeezHiltApp : Application() diff --git a/app/src/main/java/be/ugent/sel/studeez/activities/MainActivity.kt b/app/src/main/java/be/ugent/sel/studeez/activities/MainActivity.kt index 2af2ec1..318fe7a 100644 --- a/app/src/main/java/be/ugent/sel/studeez/activities/MainActivity.kt +++ b/app/src/main/java/be/ugent/sel/studeez/activities/MainActivity.kt @@ -10,8 +10,11 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview +import be.ugent.sel.studeez.StudeezApp import be.ugent.sel.studeez.ui.theme.StudeezTheme +import dagger.hilt.android.AndroidEntryPoint +@AndroidEntryPoint class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -22,7 +25,7 @@ class MainActivity : ComponentActivity() { modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background ) { - Greeting("Android") + StudeezApp() } } } diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/ButtonComposable.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/ButtonComposable.kt new file mode 100644 index 0000000..e174ef2 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/ButtonComposable.kt @@ -0,0 +1,46 @@ +package be.ugent.sel.studeez.common.composable + +import androidx.annotation.StringRes +import androidx.compose.material.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.sp + +@Composable +fun BasicTextButton(@StringRes text: Int, modifier: Modifier, action: () -> Unit) { + TextButton(onClick = action, modifier = modifier) { Text(text = stringResource(text)) } +} + +@Composable +fun BasicButton(@StringRes text: Int, modifier: Modifier, action: () -> Unit) { + Button( + onClick = action, + modifier = modifier + ) { + Text(text = stringResource(text), fontSize = 16.sp) + } +} + +@Composable +fun DialogConfirmButton(@StringRes text: Int, action: () -> Unit) { + Button( + onClick = action + ) { + Text(text = stringResource(text)) + } +} + +@Composable +fun DialogCancelButton(@StringRes text: Int, action: () -> Unit) { + Button( + onClick = action, + colors = + ButtonDefaults.buttonColors( + backgroundColor = MaterialTheme.colors.onPrimary, + contentColor = MaterialTheme.colors.primary + ) + ) { + Text(text = stringResource(text)) + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/TextFieldComposable.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/TextFieldComposable.kt new file mode 100644 index 0000000..0b39a24 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/TextFieldComposable.kt @@ -0,0 +1,102 @@ +package be.ugent.sel.studeez.common.composable + +import androidx.annotation.StringRes +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Email +import androidx.compose.material.icons.filled.Lock +import androidx.compose.runtime.* +import be.ugent.sel.studeez.R.string as AppText +import be.ugent.sel.studeez.R.drawable as AppIcon +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation + +@Composable +fun BasicField( + @StringRes text: Int, + value: String, + onNewValue: (String) -> Unit, + modifier: Modifier = Modifier +) { + OutlinedTextField( + singleLine = true, + modifier = modifier, + value = value, + onValueChange = { onNewValue(it) }, + placeholder = { Text(stringResource(text)) } + ) +} + +@Composable +fun EmailField( + value: String, + onNewValue: (String) -> Unit, + modifier: Modifier = Modifier +) { + OutlinedTextField( + singleLine = true, + modifier = modifier, + value = value, + onValueChange = { onNewValue(it) }, + placeholder = { Text(stringResource(AppText.email)) }, + leadingIcon = { Icon(imageVector = Icons.Default.Email, contentDescription = "Email") } + ) +} + +@Composable +fun PasswordField( + value: String, + onNewValue: (String) -> Unit, + modifier: Modifier = Modifier +) { + PasswordField(value, AppText.password, onNewValue, modifier) +} + +@Composable +fun RepeatPasswordField( + value: String, + onNewValue: (String) -> Unit, + modifier: Modifier = Modifier +) { + PasswordField(value, AppText.repeat_password, onNewValue, modifier) +} + +@Composable +private fun PasswordField( + value: String, + @StringRes placeholder: Int, + onNewValue: (String) -> Unit, + modifier: Modifier = Modifier +) { + var isVisible by remember { mutableStateOf(false) } + + val icon = + if (isVisible) painterResource(AppIcon.ic_visibility_on) + else painterResource(AppIcon.ic_visibility_off) + + val visualTransformation = + if (isVisible) VisualTransformation.None else PasswordVisualTransformation() + + OutlinedTextField( + modifier = modifier, + value = value, + onValueChange = { onNewValue(it) }, + placeholder = { Text(text = stringResource(placeholder)) }, + leadingIcon = { Icon(imageVector = Icons.Default.Lock, contentDescription = "Lock") }, + trailingIcon = { + IconButton(onClick = { isVisible = !isVisible }) { + Icon(painter = icon, contentDescription = "Visibility") + } + }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), + visualTransformation = visualTransformation + ) +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/common/ext/ModifierExt.kt b/app/src/main/java/be/ugent/sel/studeez/common/ext/ModifierExt.kt new file mode 100644 index 0000000..7280ab3 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/common/ext/ModifierExt.kt @@ -0,0 +1,42 @@ +package be.ugent.sel.studeez.common.ext + +import androidx.compose.foundation.layout.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +fun Modifier.textButton(): Modifier { + return this.fillMaxWidth().padding(16.dp, 8.dp, 16.dp, 0.dp) +} + +fun Modifier.basicButton(): Modifier { + return this.fillMaxWidth().padding(16.dp, 8.dp) +} + +fun Modifier.card(): Modifier { + return this.padding(16.dp, 0.dp, 16.dp, 8.dp) +} + +fun Modifier.contextMenu(): Modifier { + return this.wrapContentWidth() +} + +fun Modifier.dropdownSelector(): Modifier { + return this.fillMaxWidth() +} + +fun Modifier.fieldModifier(): Modifier { + return this.fillMaxWidth().padding(16.dp, 4.dp) +} + +fun Modifier.toolbarActions(): Modifier { + return this.wrapContentSize(Alignment.TopEnd) +} + +fun Modifier.spacer(): Modifier { + return this.fillMaxWidth().padding(12.dp) +} + +fun Modifier.smallSpacer(): Modifier { + return this.fillMaxWidth().height(8.dp) +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/common/ext/StringExt.kt b/app/src/main/java/be/ugent/sel/studeez/common/ext/StringExt.kt new file mode 100644 index 0000000..02af993 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/common/ext/StringExt.kt @@ -0,0 +1,25 @@ +package be.ugent.sel.studeez.common.ext + +import android.util.Patterns +import java.util.regex.Pattern + +private const val MIN_PASS_LENGTH = 6 +private const val PASS_PATTERN = "^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=\\S+$).{4,}$" + +fun String.isValidEmail(): Boolean { + return this.isNotBlank() && Patterns.EMAIL_ADDRESS.matcher(this).matches() +} + +fun String.isValidPassword(): Boolean { + return this.isNotBlank() && + this.length >= MIN_PASS_LENGTH && + Pattern.compile(PASS_PATTERN).matcher(this).matches() +} + +fun String.passwordMatches(repeated: String): Boolean { + return this == repeated +} + +fun String.idFromParameter(): String { + return this.substring(1, this.length - 1) +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/common/snackbar/SnackBarManager.kt b/app/src/main/java/be/ugent/sel/studeez/common/snackbar/SnackBarManager.kt new file mode 100644 index 0000000..ad743d5 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/common/snackbar/SnackBarManager.kt @@ -0,0 +1,20 @@ +package be.ugent.sel.studeez.common.snackbar + +import androidx.annotation.StringRes +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +object SnackbarManager { + private val messages: MutableStateFlow = MutableStateFlow(null) + val snackbarMessages: StateFlow + get() = messages.asStateFlow() + + fun showMessage(@StringRes message: Int) { + messages.value = SnackbarMessage.ResourceSnackbar(message) + } + + fun showMessage(message: SnackbarMessage) { + messages.value = message + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/common/snackbar/SnackBarMessage.kt b/app/src/main/java/be/ugent/sel/studeez/common/snackbar/SnackBarMessage.kt new file mode 100644 index 0000000..495ceb1 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/common/snackbar/SnackBarMessage.kt @@ -0,0 +1,25 @@ +package be.ugent.sel.studeez.common.snackbar + +import android.content.res.Resources +import androidx.annotation.StringRes +import be.ugent.sel.studeez.R.string as AppText + +sealed class SnackbarMessage { + class StringSnackbar(val message: String) : SnackbarMessage() + class ResourceSnackbar(@StringRes val message: Int) : SnackbarMessage() + + companion object { + fun SnackbarMessage.toMessage(resources: Resources): String { + return when (this) { + is StringSnackbar -> this.message + is ResourceSnackbar -> resources.getString(this.message) + } + } + + fun Throwable.toSnackbarMessage(): SnackbarMessage { + val message = this.message.orEmpty() + return if (message.isNotBlank()) StringSnackbar(message) + else ResourceSnackbar(AppText.generic_error) + } + } +} diff --git a/app/src/main/java/be/ugent/sel/studeez/di/.keep b/app/src/main/java/be/ugent/sel/studeez/di/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/main/java/be/ugent/sel/studeez/di/DatabaseModule.kt b/app/src/main/java/be/ugent/sel/studeez/di/DatabaseModule.kt new file mode 100644 index 0000000..ffad868 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/di/DatabaseModule.kt @@ -0,0 +1,19 @@ +package be.ugent.sel.studeez.di + +import be.ugent.sel.studeez.domain.AccountDAO +import be.ugent.sel.studeez.domain.LogService +import be.ugent.sel.studeez.domain.implementation.FirebaseAccountDAO +import be.ugent.sel.studeez.domain.implementation.LogServiceImpl +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +abstract class DatabaseModule { + @Binds abstract fun provideAccountDAO(impl: FirebaseAccountDAO): AccountDAO + + @Binds abstract fun provideLogService(impl: LogServiceImpl): LogService + +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/di/FireBaseModule.kt b/app/src/main/java/be/ugent/sel/studeez/di/FireBaseModule.kt new file mode 100644 index 0000000..e8510f3 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/di/FireBaseModule.kt @@ -0,0 +1,21 @@ +package be.ugent.sel.studeez.di + +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.auth.ktx.auth +import com.google.firebase.firestore.FirebaseFirestore +import com.google.firebase.firestore.ktx.firestore +import com.google.firebase.ktx.Firebase +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +object FirebaseModule { + @Provides + fun auth(): FirebaseAuth = Firebase.auth + + @Provides + fun firestore(): FirebaseFirestore = Firebase.firestore +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/domain/AccountDAO.kt b/app/src/main/java/be/ugent/sel/studeez/domain/AccountDAO.kt index 9188b15..96ecb74 100644 --- a/app/src/main/java/be/ugent/sel/studeez/domain/AccountDAO.kt +++ b/app/src/main/java/be/ugent/sel/studeez/domain/AccountDAO.kt @@ -25,10 +25,9 @@ interface AccountDAO { val currentUser: Flow - suspend fun authenticate(email: String, password: String) + suspend fun signInWithEmailAndPassword(email: String, password: String) suspend fun sendRecoveryEmail(email: String) - suspend fun createAnonymousAccount() - suspend fun linkAccount(email: String, password: String) + suspend fun signUpWithEmailAndPassword(email: String, password: String) suspend fun deleteAccount() suspend fun signOut() } diff --git a/app/src/main/java/be/ugent/sel/studeez/domain/LogService.kt b/app/src/main/java/be/ugent/sel/studeez/domain/LogService.kt new file mode 100644 index 0000000..c5b7087 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/domain/LogService.kt @@ -0,0 +1,5 @@ +package be.ugent.sel.studeez.domain + +interface LogService { + fun logNonFatalCrash(throwable: Throwable) +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FirebaseAccountDAO.kt b/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FirebaseAccountDAO.kt index 83bab49..4067fdd 100644 --- a/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FirebaseAccountDAO.kt +++ b/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FirebaseAccountDAO.kt @@ -47,7 +47,7 @@ class FirebaseAccountDAO @Inject constructor( awaitClose { auth.removeAuthStateListener(listener) } } - override suspend fun authenticate(email: String, password: String) { + override suspend fun signInWithEmailAndPassword(email: String, password: String) { auth.signInWithEmailAndPassword(email, password).await() } @@ -55,31 +55,15 @@ class FirebaseAccountDAO @Inject constructor( auth.sendPasswordResetEmail(email).await() } - override suspend fun createAnonymousAccount() { - auth.signInAnonymously().await() + override suspend fun signUpWithEmailAndPassword(email: String, password: String) { + auth.createUserWithEmailAndPassword(email, password).await() } - override suspend fun linkAccount(email: String, password: String): Unit = - trace(LINK_ACCOUNT_TRACE) { - val credential = EmailAuthProvider.getCredential(email, password) - auth.currentUser!!.linkWithCredential(credential).await() - } - override suspend fun deleteAccount() { auth.currentUser!!.delete().await() } override suspend fun signOut() { - if (auth.currentUser!!.isAnonymous) { - auth.currentUser!!.delete() - } auth.signOut() - - // Sign the user back in anonymously. - createAnonymousAccount() - } - - companion object { - private const val LINK_ACCOUNT_TRACE = "linkAccount" } } diff --git a/app/src/main/java/be/ugent/sel/studeez/domain/implementation/LogServiceImpl.kt b/app/src/main/java/be/ugent/sel/studeez/domain/implementation/LogServiceImpl.kt new file mode 100644 index 0000000..1697872 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/domain/implementation/LogServiceImpl.kt @@ -0,0 +1,14 @@ +package be.ugent.sel.studeez.domain.implementation + +import be.ugent.sel.studeez.domain.LogService +import com.google.firebase.crashlytics.ktx.crashlytics +import com.google.firebase.ktx.Firebase +import javax.inject.Inject + +class LogServiceImpl @Inject constructor() : LogService { + override fun logNonFatalCrash(throwable: Throwable) { + Firebase.crashlytics.recordException(throwable) + } + + +} diff --git a/app/src/main/java/be/ugent/sel/studeez/navigation/.keep b/app/src/main/java/be/ugent/sel/studeez/navigation/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/main/java/be/ugent/sel/studeez/navigation/StudeezDestinations.kt b/app/src/main/java/be/ugent/sel/studeez/navigation/StudeezDestinations.kt new file mode 100644 index 0000000..861b0a3 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/navigation/StudeezDestinations.kt @@ -0,0 +1,8 @@ +package be.ugent.sel.studeez.navigation + +object StudeezDestinations { + const val SPLASH_SCREEN = "splash" + const val SIGN_UP_SCREEN = "signup" + const val LOGIN_SCREEN = "login" + +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/.keep b/app/src/main/java/be/ugent/sel/studeez/screens/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/StudeezViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/StudeezViewModel.kt new file mode 100644 index 0000000..2db85f2 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/StudeezViewModel.kt @@ -0,0 +1,23 @@ +package be.ugent.sel.studeez.screens + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import be.ugent.sel.studeez.common.snackbar.SnackbarManager +import be.ugent.sel.studeez.common.snackbar.SnackbarMessage.Companion.toSnackbarMessage +import be.ugent.sel.studeez.domain.LogService +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +open class StudeezViewModel(private val logService: LogService) : ViewModel() { + fun launchCatching(snackbar: Boolean = true, block: suspend CoroutineScope.() -> Unit) = + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + if (snackbar) { + SnackbarManager.showMessage(throwable.toSnackbarMessage()) + } + logService.logNonFatalCrash(throwable) + }, + block = block + ) +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/splash/SplashScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/splash/SplashScreen.kt new file mode 100644 index 0000000..ec5e515 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/splash/SplashScreen.kt @@ -0,0 +1,55 @@ +package be.ugent.sel.studeez.screens.splash + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.hilt.navigation.compose.hiltViewModel +import be.ugent.sel.studeez.common.composable.BasicButton +import be.ugent.sel.studeez.common.ext.basicButton +import kotlinx.coroutines.delay +import be.ugent.sel.studeez.R.string as AppText + +private const val SPLASH_TIMEOUT = 1000L + +@Composable +fun SplashScreen( + openAndPopUp: (String, String) -> Unit, + modifier: Modifier = Modifier, + viewModel: SplashViewModel = hiltViewModel() +) { + Column( + modifier = + modifier + .fillMaxWidth() + .fillMaxHeight() + .background(color = MaterialTheme.colors.background) + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + if (viewModel.showError.value) { + Text(text = stringResource(AppText.generic_error)) + + BasicButton(AppText.try_again, Modifier.basicButton()) { viewModel.onAppStart(openAndPopUp) } + } else { + CircularProgressIndicator(color = MaterialTheme.colors.onBackground) + } + } + + LaunchedEffect(true) { + delay(SPLASH_TIMEOUT) + viewModel.onAppStart(openAndPopUp) + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/splash/SplashViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/splash/SplashViewModel.kt new file mode 100644 index 0000000..f019c8c --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/splash/SplashViewModel.kt @@ -0,0 +1,26 @@ +package be.ugent.sel.studeez.screens.splash + +import androidx.compose.runtime.mutableStateOf +import be.ugent.sel.studeez.domain.AccountDAO +import be.ugent.sel.studeez.domain.LogService +import be.ugent.sel.studeez.screens.StudeezViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class SplashViewModel @Inject constructor( + private val accountDAO: AccountDAO, + logService: LogService +) : StudeezViewModel(logService) { + val showError = mutableStateOf(false) + + fun onAppStart(openAndPopUp: (String, String) -> Unit) { + + showError.value = false + if (accountDAO.hasUser) { + // openAndPopUp( , SPLASH_SCREEN) + } else{ + // openAndPopUp(, SPLASH_SCREEN) + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_visibility_off.xml b/app/src/main/res/drawable/ic_visibility_off.xml new file mode 100644 index 0000000..92c4856 --- /dev/null +++ b/app/src/main/res/drawable/ic_visibility_off.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_visibility_on.xml b/app/src/main/res/drawable/ic_visibility_on.xml new file mode 100644 index 0000000..a3e222a --- /dev/null +++ b/app/src/main/res/drawable/ic_visibility_on.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c461410..79d0a41 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,26 @@ + Studeez + Email + Password + Repeat password + Something wrong happened. Please try again. + Please insert a valid email. + Cancel + Try again + + + Create account + Your password should have at least six characters and include one digit, one lower case letter and one upper case letter. + Passwords do not match. + Already have an account? Log in. + + + Don\'t have an account yet? Sign up. + Sign in + Enter your login details + Forgot password? Click to get recovery email + Check your inbox for the recovery email. + Password cannot be empty. + \ No newline at end of file