diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/host/MainActivity.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/host/MainActivity.kt index cd72eea..8457e99 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/host/MainActivity.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/host/MainActivity.kt @@ -16,12 +16,15 @@ import androidx.navigation.compose.rememberNavController import dagger.hilt.android.AndroidEntryPoint import ing.bikeshedengineer.debtpirate.auth.presentation.login.LoginScreen import ing.bikeshedengineer.debtpirate.auth.presentation.register.RegistrationScreen +import ing.bikeshedengineer.debtpirate.domain.usecase.pref.UpdateCurrentRouteUseCase import ing.bikeshedengineer.debtpirate.navigation.Destination import ing.bikeshedengineer.debtpirate.navigation.NavigationAction import ing.bikeshedengineer.debtpirate.navigation.Navigator import ing.bikeshedengineer.debtpirate.theme.DebtPirateTheme +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import javax.inject.Inject @@ -30,6 +33,9 @@ class MainActivity : ComponentActivity() { @Inject lateinit var navigator: Navigator + @Inject + lateinit var updateCurrentRoute: UpdateCurrentRouteUseCase + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() @@ -40,9 +46,14 @@ class MainActivity : ComponentActivity() { when (action) { is NavigationAction.Navigate -> { navController.navigate(action.destination) { + val scope = CoroutineScope(context = Dispatchers.IO) + scope.launch { + updateCurrentRoute(action.destination) + } action.navOptions(this) } } + NavigationAction.NavigateUp -> { navController.navigateUp() } @@ -51,8 +62,8 @@ class MainActivity : ComponentActivity() { DebtPirateTheme { NavHost(navController = navController, startDestination = Destination.AuthGraph) { - navigation(startDestination = Destination.LoginScreen) { - composable() { LoginScreen(navController = navController) } + navigation(startDestination = Destination.RegistrationScreen) { + composable() { LoginScreen() } composable() { RegistrationScreen() } } } diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreen.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreen.kt index 172d5cd..45a1d64 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreen.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreen.kt @@ -45,21 +45,8 @@ import ing.bikeshedengineer.debtpirate.R @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @Composable fun LoginScreen( - navController: NavController, viewModel: LoginScreenViewModel = hiltViewModel() ) { -// LaunchedEffect(viewModel.navigationEvent) { -// viewModel.navigationEvent.collect { event -> -// when (event) { -// is AuthNavigationEvent.NavigateToRegistrationScreen -> { -// navController.navigate(RegistrationRoute) -// } -// -// else -> {} -// } -// } -// } - Scaffold( modifier = Modifier.fillMaxSize() ) { @@ -77,6 +64,7 @@ fun LoginScreen( ) { val emailAddress = viewModel.emailAddress.collectAsState() val password = viewModel.password.collectAsState() + LoginComponent( emailAddress = emailAddress, onUpdateEmailAddress = viewModel::updateUsername, diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreenViewModel.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreenViewModel.kt index 10c1421..a2fb88a 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreenViewModel.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreenViewModel.kt @@ -38,7 +38,6 @@ enum class InvalidReason { @HiltViewModel class LoginScreenViewModel @Inject constructor( private val navigator: Navigator, - private val authRepository: AuthRepository, private val prefsStore: DataStore, private val submitLoginCredentials: SubmitLoginCredentialsUseCase, private val validateLoginCredentials: ValidateLoginCredentialsUseCase, diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/register/RegistrationScreen.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/register/RegistrationScreen.kt index 6ecd6f8..656ebc5 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/register/RegistrationScreen.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/register/RegistrationScreen.kt @@ -1,39 +1,208 @@ package ing.bikeshedengineer.debtpirate.auth.presentation.register import android.annotation.SuppressLint -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.ArrowBack +import androidx.compose.material.icons.outlined.Mail +import androidx.compose.material.icons.outlined.Password +import androidx.compose.material.icons.outlined.Person +import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MediumTopAppBar +import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardCapitalization +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import ing.bikeshedengineer.debtpirate.R +@OptIn(ExperimentalMaterial3Api::class) @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @Composable() -fun RegistrationScreen() { +fun RegistrationScreen(viewModel: RegistrationScreenViewModel = hiltViewModel()) { + val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) + Scaffold( - modifier = Modifier.fillMaxSize() - ) { - Column { - RegistrationScreenTopAppBar( - modifier = Modifier.fillMaxWidth() - .weight(1f) - ) + topBar = { RegistrationTopAppBar(onNavigateUp = viewModel::navigateUp) }, + modifier = Modifier + .fillMaxSize() + .nestedScroll(scrollBehavior.nestedScrollConnection) + ) { innerPadding -> + Column( + modifier = Modifier + .padding(innerPadding) + .padding(16.dp) + ) { + RegistrationComponent() } } } +@OptIn(ExperimentalMaterial3Api::class) @Composable -private fun RegistrationScreenTopAppBar(modifier: Modifier = Modifier) { - Box(modifier = modifier.background(Color.Green)) { +private fun RegistrationTopAppBar(onNavigateUp: () -> Unit, modifier: Modifier = Modifier) { + MediumTopAppBar( + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.primaryContainer, + titleContentColor = MaterialTheme.colorScheme.primary, + ), + title = { + Text( + stringResource(R.string.auth__createAccount), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + }, + navigationIcon = { + IconButton(onClick = onNavigateUp) { + Icon( + imageVector = Icons.AutoMirrored.Outlined.ArrowBack, + contentDescription = "back" + ) + } + } + ) +} + +@Composable +private fun RegistrationComponent(modifier: Modifier = Modifier) { + Column(modifier = modifier.verticalScroll(rememberScrollState())) { + OutlinedTextField( + value = "", + label = { Text(text = stringResource(R.string.auth__email)) }, + placeholder = { Text(text = stringResource(R.string.auth__email)) }, + leadingIcon = { Icon(Icons.Outlined.Mail, stringResource(R.string.auth__email)) }, + singleLine = true, + keyboardOptions = KeyboardOptions( + capitalization = KeyboardCapitalization.None, + keyboardType = KeyboardType.Email, + imeAction = ImeAction.Next + ), + onValueChange = {}, + modifier = Modifier.fillMaxWidth() + ) + + OutlinedTextField( + value = "", + label = { Text(text = stringResource(R.string.auth__name)) }, + placeholder = { Text(text = stringResource(R.string.auth__name)) }, + leadingIcon = { Icon(Icons.Outlined.Person, stringResource(R.string.auth__name)) }, + singleLine = true, + keyboardOptions = KeyboardOptions( + capitalization = KeyboardCapitalization.Words, + keyboardType = KeyboardType.Text, + imeAction = ImeAction.Next + ), + onValueChange = {}, + modifier = Modifier + .fillMaxWidth() + .padding(PaddingValues(top = 4.dp)) + ) + + OutlinedTextField( + value = "", + label = { Text(text = stringResource(R.string.auth__password)) }, + placeholder = { Text(text = stringResource(R.string.auth__password)) }, + leadingIcon = { + Icon( + Icons.Outlined.Password, + stringResource(R.string.auth__password) + ) + }, + singleLine = true, + keyboardOptions = KeyboardOptions( + capitalization = KeyboardCapitalization.None, + keyboardType = KeyboardType.Password, + imeAction = ImeAction.Next + ), + onValueChange = {}, + modifier = Modifier + .fillMaxWidth() + .padding(PaddingValues(top = 4.dp)) + ) + + OutlinedTextField( + value = "", + label = { Text(text = stringResource(R.string.auth__confirmPassword)) }, + placeholder = { Text(text = stringResource(R.string.auth__confirmPassword)) }, + leadingIcon = { + Icon( + Icons.Outlined.Password, + stringResource(R.string.auth__confirmPassword) + ) + }, + singleLine = true, + keyboardOptions = KeyboardOptions( + capitalization = KeyboardCapitalization.None, + keyboardType = KeyboardType.Password, + imeAction = ImeAction.Done + ), + onValueChange = {}, + modifier = Modifier + .fillMaxWidth() + .padding(PaddingValues(top = 4.dp)) + ) + + var linkStyle = SpanStyle(color = Color.Blue, textDecoration = TextDecoration.Underline) Text( - text = "Hello from Registration! I'm in a box!", - modifier = Modifier.align(Alignment.Center) + modifier = Modifier.padding(paddingValues = PaddingValues(top = 16.dp, bottom = 16.dp)), + text = buildAnnotatedString { + append("By registering, you agree to the ") + + withStyle(style = linkStyle) { + append("Terms & Conditions") + } + + append(" and ") + + withStyle(style = linkStyle) { + append("Privacy Policy") + } + + append(".") + }, + softWrap = true + ) + + RegisterButton() + } +} + +@Composable +private fun RegisterButton() { + Button( + onClick = { }, + modifier = Modifier + .fillMaxWidth() + ) { + Text( + "Register" ) } } \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/register/RegistrationScreenViewModel.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/register/RegistrationScreenViewModel.kt index e4ddddd..9bcabde 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/register/RegistrationScreenViewModel.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/register/RegistrationScreenViewModel.kt @@ -1,4 +1,19 @@ package ing.bikeshedengineer.debtpirate.auth.presentation.register -class RegistrationScreenViewModel { +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import ing.bikeshedengineer.debtpirate.navigation.Navigator +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class RegistrationScreenViewModel @Inject constructor( + private val navigator: Navigator +) : ViewModel() { + fun navigateUp() { + viewModelScope.launch { + navigator.navigateUp() + } + } } \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/usecase/pref/UpdateCurrentRouteUseCase.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/usecase/pref/UpdateCurrentRouteUseCase.kt new file mode 100644 index 0000000..6fbe03b --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/usecase/pref/UpdateCurrentRouteUseCase.kt @@ -0,0 +1,18 @@ +package ing.bikeshedengineer.debtpirate.domain.usecase.pref + +import androidx.datastore.core.DataStore +import ing.bikeshedengineer.debtpirate.PrefsDataStore +import ing.bikeshedengineer.debtpirate.navigation.Destination +import javax.inject.Inject + +class UpdateCurrentRouteUseCase @Inject constructor( + private val store: DataStore +) { + suspend operator fun invoke(destination: Destination) { + store.updateData { data -> + data.toBuilder() + .setCurrentRoute(destination.toString()) + .build() + } + } +} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/navigation/Navigator.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/navigation/Navigator.kt index ea0ee8c..cb67f20 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/navigation/Navigator.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/navigation/Navigator.kt @@ -4,7 +4,6 @@ import androidx.navigation.NavOptionsBuilder import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.receiveAsFlow -import javax.inject.Inject interface Navigator { val startDestination: Destination @@ -16,7 +15,7 @@ interface Navigator { } class NavigatorImpl( - override val startDestination: Destination + override val startDestination: Destination, ) : Navigator { private val _navigationActions = Channel() override val navigationActions = _navigationActions.receiveAsFlow() diff --git a/app/app/src/main/proto/prefs_data_store.proto b/app/app/src/main/proto/prefs_data_store.proto index 43bbd9f..2d229c6 100644 --- a/app/app/src/main/proto/prefs_data_store.proto +++ b/app/app/src/main/proto/prefs_data_store.proto @@ -9,7 +9,8 @@ message Token { } message PrefsDataStore { - int32 user_id = 1; - Token auth_token = 2; - Token session_token = 3; + string current_route = 1; + int32 user_id = 2; + Token auth_token = 3; + Token session_token = 4; } \ No newline at end of file diff --git a/app/app/src/main/res/values/strings.xml b/app/app/src/main/res/values/strings.xml index d218441..54dbca3 100644 --- a/app/app/src/main/res/values/strings.xml +++ b/app/app/src/main/res/values/strings.xml @@ -2,4 +2,7 @@ Debt Pirate Email Password + Confirm Password + Create Account + Name \ No newline at end of file