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 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<Destination.AuthGraph>(startDestination = Destination.LoginScreen) {
composable<Destination.LoginScreen>() { LoginScreen(navController = navController) }
navigation<Destination.AuthGraph>(startDestination = Destination.RegistrationScreen) {
composable<Destination.LoginScreen>() { LoginScreen() }
composable<Destination.RegistrationScreen>() { RegistrationScreen() }
}
}

View file

@ -45,21 +45,8 @@ import ing.bikeshedengineer.debtpirate.R
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@Composable
fun LoginScreen(
navController: NavController,
viewModel: LoginScreenViewModel = hiltViewModel<LoginScreenViewModel>()
) {
// 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,

View file

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

View file

@ -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<RegistrationScreenViewModel>()) {
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"
)
}
}

View file

@ -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()
}
}
}

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.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<NavigationAction>()
override val navigationActions = _navigationActions.receiveAsFlow()

View file

@ -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;
}

View file

@ -2,4 +2,7 @@
<string name="app_name">Debt Pirate</string>
<string name="auth__email">Email</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>