Finish laying out registration screen
This commit is contained in:
parent
dd0cc9ba2f
commit
149ce13e3e
9 changed files with 240 additions and 37 deletions
|
@ -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() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
|
@ -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>
|
Loading…
Add table
Reference in a new issue