Finish laying out registration screen

This commit is contained in:
Z. Charles Dziura 2024-11-02 10:35:51 -04:00
parent dd0cc9ba2f
commit 149ce13e3e
9 changed files with 240 additions and 37 deletions

View file

@ -16,12 +16,15 @@ import androidx.navigation.compose.rememberNavController
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import ing.bikeshedengineer.debtpirate.auth.presentation.login.LoginScreen import ing.bikeshedengineer.debtpirate.auth.presentation.login.LoginScreen
import ing.bikeshedengineer.debtpirate.auth.presentation.register.RegistrationScreen 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.Destination
import ing.bikeshedengineer.debtpirate.navigation.NavigationAction import ing.bikeshedengineer.debtpirate.navigation.NavigationAction
import ing.bikeshedengineer.debtpirate.navigation.Navigator import ing.bikeshedengineer.debtpirate.navigation.Navigator
import ing.bikeshedengineer.debtpirate.theme.DebtPirateTheme import ing.bikeshedengineer.debtpirate.theme.DebtPirateTheme
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import javax.inject.Inject import javax.inject.Inject
@ -30,6 +33,9 @@ class MainActivity : ComponentActivity() {
@Inject @Inject
lateinit var navigator: Navigator lateinit var navigator: Navigator
@Inject
lateinit var updateCurrentRoute: UpdateCurrentRouteUseCase
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
@ -40,9 +46,14 @@ class MainActivity : ComponentActivity() {
when (action) { when (action) {
is NavigationAction.Navigate -> { is NavigationAction.Navigate -> {
navController.navigate(action.destination) { navController.navigate(action.destination) {
val scope = CoroutineScope(context = Dispatchers.IO)
scope.launch {
updateCurrentRoute(action.destination)
}
action.navOptions(this) action.navOptions(this)
} }
} }
NavigationAction.NavigateUp -> { NavigationAction.NavigateUp -> {
navController.navigateUp() navController.navigateUp()
} }
@ -51,8 +62,8 @@ class MainActivity : ComponentActivity() {
DebtPirateTheme { DebtPirateTheme {
NavHost(navController = navController, startDestination = Destination.AuthGraph) { NavHost(navController = navController, startDestination = Destination.AuthGraph) {
navigation<Destination.AuthGraph>(startDestination = Destination.LoginScreen) { navigation<Destination.AuthGraph>(startDestination = Destination.RegistrationScreen) {
composable<Destination.LoginScreen>() { LoginScreen(navController = navController) } composable<Destination.LoginScreen>() { LoginScreen() }
composable<Destination.RegistrationScreen>() { RegistrationScreen() } composable<Destination.RegistrationScreen>() { RegistrationScreen() }
} }
} }

View file

@ -45,21 +45,8 @@ import ing.bikeshedengineer.debtpirate.R
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@Composable @Composable
fun LoginScreen( fun LoginScreen(
navController: NavController,
viewModel: LoginScreenViewModel = hiltViewModel<LoginScreenViewModel>() viewModel: LoginScreenViewModel = hiltViewModel<LoginScreenViewModel>()
) { ) {
// LaunchedEffect(viewModel.navigationEvent) {
// viewModel.navigationEvent.collect { event ->
// when (event) {
// is AuthNavigationEvent.NavigateToRegistrationScreen -> {
// navController.navigate(RegistrationRoute)
// }
//
// else -> {}
// }
// }
// }
Scaffold( Scaffold(
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { ) {
@ -77,6 +64,7 @@ fun LoginScreen(
) { ) {
val emailAddress = viewModel.emailAddress.collectAsState() val emailAddress = viewModel.emailAddress.collectAsState()
val password = viewModel.password.collectAsState() val password = viewModel.password.collectAsState()
LoginComponent( LoginComponent(
emailAddress = emailAddress, emailAddress = emailAddress,
onUpdateEmailAddress = viewModel::updateUsername, onUpdateEmailAddress = viewModel::updateUsername,

View file

@ -38,7 +38,6 @@ enum class InvalidReason {
@HiltViewModel @HiltViewModel
class LoginScreenViewModel @Inject constructor( class LoginScreenViewModel @Inject constructor(
private val navigator: Navigator, private val navigator: Navigator,
private val authRepository: AuthRepository,
private val prefsStore: DataStore<PrefsDataStore>, private val prefsStore: DataStore<PrefsDataStore>,
private val submitLoginCredentials: SubmitLoginCredentialsUseCase, private val submitLoginCredentials: SubmitLoginCredentialsUseCase,
private val validateLoginCredentials: ValidateLoginCredentialsUseCase, private val validateLoginCredentials: ValidateLoginCredentialsUseCase,

View file

@ -1,39 +1,208 @@
package ing.bikeshedengineer.debtpirate.auth.presentation.register package ing.bikeshedengineer.debtpirate.auth.presentation.register
import android.annotation.SuppressLint 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.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth 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.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color 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") @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@Composable() @Composable()
fun RegistrationScreen() { fun RegistrationScreen(viewModel: RegistrationScreenViewModel = hiltViewModel<RegistrationScreenViewModel>()) {
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold( Scaffold(
modifier = Modifier.fillMaxSize() topBar = { RegistrationTopAppBar(onNavigateUp = viewModel::navigateUp) },
modifier = Modifier
.fillMaxSize()
.nestedScroll(scrollBehavior.nestedScrollConnection)
) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.padding(16.dp)
) { ) {
Column { RegistrationComponent()
RegistrationScreenTopAppBar( }
modifier = Modifier.fillMaxWidth() }
.weight(1f) }
@OptIn(ExperimentalMaterial3Api::class)
@Composable
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(
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 @Composable
private fun RegistrationScreenTopAppBar(modifier: Modifier = Modifier) { private fun RegisterButton() {
Box(modifier = modifier.background(Color.Green)) { Button(
onClick = { },
modifier = Modifier
.fillMaxWidth()
) {
Text( Text(
text = "Hello from Registration! I'm in a box!", "Register"
modifier = Modifier.align(Alignment.Center)
) )
} }
} }

View file

@ -1,4 +1,19 @@
package ing.bikeshedengineer.debtpirate.auth.presentation.register 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()
}
}
} }

View file

@ -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<PrefsDataStore>
) {
suspend operator fun invoke(destination: Destination) {
store.updateData { data ->
data.toBuilder()
.setCurrentRoute(destination.toString())
.build()
}
}
}

View file

@ -4,7 +4,6 @@ import androidx.navigation.NavOptionsBuilder
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.receiveAsFlow
import javax.inject.Inject
interface Navigator { interface Navigator {
val startDestination: Destination val startDestination: Destination
@ -16,7 +15,7 @@ interface Navigator {
} }
class NavigatorImpl( class NavigatorImpl(
override val startDestination: Destination override val startDestination: Destination,
) : Navigator { ) : Navigator {
private val _navigationActions = Channel<NavigationAction>() private val _navigationActions = Channel<NavigationAction>()
override val navigationActions = _navigationActions.receiveAsFlow() override val navigationActions = _navigationActions.receiveAsFlow()

View file

@ -9,7 +9,8 @@ message Token {
} }
message PrefsDataStore { message PrefsDataStore {
int32 user_id = 1; string current_route = 1;
Token auth_token = 2; int32 user_id = 2;
Token session_token = 3; Token auth_token = 3;
Token session_token = 4;
} }

View file

@ -2,4 +2,7 @@
<string name="app_name">Debt Pirate</string> <string name="app_name">Debt Pirate</string>
<string name="auth__email">Email</string> <string name="auth__email">Email</string>
<string name="auth__password">Password</string> <string name="auth__password">Password</string>
<string name="auth__confirmPassword">Confirm Password</string>
<string name="auth__createAccount">Create Account</string>
<string name="auth__name">Name</string>
</resources> </resources>