Start reworking credentials manager into its own dependency module
This commit is contained in:
parent
42d2705a84
commit
582e7015a9
10 changed files with 84 additions and 19 deletions
|
@ -33,6 +33,7 @@ import ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.register.Reg
|
|||
import ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.register.RegistrationScreenViewModel
|
||||
import ing.bikeshedengineer.debtpirate.app.screen.home.presentation.overview.OverviewScreen
|
||||
import ing.bikeshedengineer.debtpirate.domain.usecase.GetStoredTokensUseCase
|
||||
import ing.bikeshedengineer.debtpirate.domain.usecase.StoreCredentialsUseCase
|
||||
import ing.bikeshedengineer.debtpirate.navigation.Destination
|
||||
import ing.bikeshedengineer.debtpirate.navigation.NavigationAction
|
||||
import ing.bikeshedengineer.debtpirate.navigation.Navigator
|
||||
|
@ -51,6 +52,9 @@ class MainActivity : ComponentActivity() {
|
|||
@Inject
|
||||
lateinit var getStoredTokens: GetStoredTokensUseCase
|
||||
|
||||
@Inject
|
||||
lateinit var storeCredentials: StoreCredentialsUseCase
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
|
@ -59,6 +63,7 @@ class MainActivity : ComponentActivity() {
|
|||
enableEdgeToEdge()
|
||||
setContent {
|
||||
val navController = rememberNavController()
|
||||
|
||||
|
||||
ObserveAsEvents(navigator.navigationActions) { action ->
|
||||
when (action) {
|
||||
|
@ -102,16 +107,18 @@ class MainActivity : ComponentActivity() {
|
|||
) {
|
||||
composable<Destination.AuthLogin> {
|
||||
val viewModel = hiltViewModel<LoginScreenViewModel>()
|
||||
val toastMessages = viewModel.toastMessages.collectAsState("")
|
||||
val storeCredentialMessages = viewModel.storeCredentialsMessages.collectAsState(null);
|
||||
|
||||
LoginScreen(
|
||||
emailAddress = viewModel.emailAddress.collectAsState(""),
|
||||
isEmailAddressValid = viewModel.isEmailAddressValid.collectAsState(true),
|
||||
password = viewModel.password.collectAsState(""),
|
||||
isPasswordValid = viewModel.isPasswordValid.collectAsState(true),
|
||||
toastMessages = viewModel.toastMessages.collectAsState(""),
|
||||
onAction = viewModel::onAction,
|
||||
toastMessages = toastMessages,
|
||||
handleCredentialManagerSignIn = viewModel::handleCredentialManagerSignIn,
|
||||
onRegisterButtonClick = viewModel::onRegisterButtonClick
|
||||
onRegisterButtonClick = viewModel::onRegisterButtonClick,
|
||||
onAction = viewModel::onAction,
|
||||
)
|
||||
}
|
||||
composable<Destination.AuthRegistration> {
|
||||
|
|
|
@ -46,7 +46,6 @@ import androidx.credentials.GetCredentialResponse
|
|||
import androidx.credentials.GetPasswordOption
|
||||
import androidx.credentials.exceptions.GetCredentialException
|
||||
import androidx.credentials.exceptions.NoCredentialException
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
|
||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||
@Composable
|
||||
|
@ -56,9 +55,9 @@ fun LoginScreen(
|
|||
password: State<String>,
|
||||
isPasswordValid: State<Boolean>,
|
||||
toastMessages: State<String>,
|
||||
onAction: (LoginScreenStateAction) -> Unit,
|
||||
handleCredentialManagerSignIn: (GetCredentialResponse) -> Unit,
|
||||
onRegisterButtonClick: () -> Unit,
|
||||
onAction: (LoginScreenStateAction) -> Unit,
|
||||
) {
|
||||
val context = LocalActivity.current!!
|
||||
val credentialManager = CredentialManager.create(context)
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.login
|
||||
|
||||
sealed interface LoginScreenMessage {
|
||||
data class Toast(val message: String) : LoginScreenMessage
|
||||
data class StoreCredentials(val username: String, val password: String) : LoginScreenMessage
|
||||
}
|
|
@ -17,8 +17,8 @@ import ing.bikeshedengineer.debtpirate.navigation.Destination
|
|||
import ing.bikeshedengineer.debtpirate.navigation.Navigator
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
@ -51,6 +51,22 @@ class LoginScreenViewModel @Inject constructor(
|
|||
val isEmailAddressValid = _state.map { it.isEmailAddressValid }
|
||||
val isPasswordValid = _state.map { it.isPasswordValid }
|
||||
|
||||
private val _messages = MutableSharedFlow<LoginScreenMessage>()
|
||||
val toastMessages = _messages
|
||||
.filter { message ->
|
||||
message is LoginScreenMessage.Toast
|
||||
}
|
||||
.map { message -> (message as LoginScreenMessage.Toast).message }
|
||||
|
||||
val storeCredentialsMessages = _messages
|
||||
.filter { message ->
|
||||
message is LoginScreenMessage.StoreCredentials
|
||||
}
|
||||
.map { message ->
|
||||
val credentialsMessage = (message as LoginScreenMessage.StoreCredentials)
|
||||
Pair(credentialsMessage.username, credentialsMessage.password)
|
||||
}
|
||||
|
||||
init {
|
||||
_state.distinctUntilChangedBy { it.emailAddress }
|
||||
.map { it.emailAddress.isNotBlank() && Patterns.EMAIL_ADDRESS.matcher(it.emailAddress).matches() }
|
||||
|
@ -77,9 +93,6 @@ class LoginScreenViewModel @Inject constructor(
|
|||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private val _toastMessages = MutableSharedFlow<String>()
|
||||
val toastMessages = _toastMessages.asSharedFlow()
|
||||
|
||||
fun onAction(action: LoginScreenStateAction) {
|
||||
when (action) {
|
||||
is LoginScreenStateAction.UpdateEmailAddress -> {
|
||||
|
@ -96,7 +109,7 @@ class LoginScreenViewModel @Inject constructor(
|
|||
|
||||
is LoginScreenStateAction.SubmitLoginRequest -> {
|
||||
viewModelScope.launch {
|
||||
onSubmitLoginRequest(_state.value.emailAddress, _state.value.password)
|
||||
onSubmitLoginRequest()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -118,9 +131,9 @@ class LoginScreenViewModel @Inject constructor(
|
|||
|
||||
}
|
||||
|
||||
private suspend fun onSubmitLoginRequest(emailAddress: String, password: String) {
|
||||
private suspend fun onSubmitLoginRequest() {
|
||||
try {
|
||||
val (userId, auth, session) = submitLoginCredentials(emailAddress, password)
|
||||
val (userId, auth, session) = submitLoginCredentials(_state.value.emailAddress, _state.value.password)
|
||||
updateStoreData(
|
||||
userId = userId,
|
||||
authToken = auth.token,
|
||||
|
@ -131,15 +144,15 @@ class LoginScreenViewModel @Inject constructor(
|
|||
} catch (err: Exception) {
|
||||
when (err) {
|
||||
is InvalidCredentialsException -> {
|
||||
_toastMessages.emit("Invalid Email Address or Password")
|
||||
_messages.emit(LoginScreenMessage.Toast("Invalid Email Address or Password"))
|
||||
}
|
||||
|
||||
is UserNotFoundException -> {
|
||||
_toastMessages.emit("User Not Found")
|
||||
_messages.emit(LoginScreenMessage.Toast("User Not Found"))
|
||||
}
|
||||
|
||||
else -> {
|
||||
_toastMessages.emit("Cannot Login, Please Try Again Later")
|
||||
_messages.emit(LoginScreenMessage.Toast("Cannot Login, Please Try Again Later"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package ing.bikeshedengineer.debtpirate.di
|
||||
|
||||
import android.content.Context
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ActivityComponent
|
||||
import dagger.hilt.android.qualifiers.ActivityContext
|
||||
import dagger.hilt.android.scopes.ActivityScoped
|
||||
import ing.bikeshedengineer.debtpirate.domain.usecase.StoreCredentialsUseCase
|
||||
|
||||
@Module
|
||||
@InstallIn(ActivityComponent::class)
|
||||
object AppCredentialsModule {
|
||||
|
||||
@Provides
|
||||
@ActivityScoped
|
||||
fun provideStoreCredentialsUseCase(@ActivityContext context: Context) = StoreCredentialsUseCase(context)
|
||||
}
|
|
@ -4,7 +4,6 @@ import dagger.Module
|
|||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import ing.bikeshedengineer.debtpirate.navigation.Destination
|
||||
import ing.bikeshedengineer.debtpirate.navigation.Navigator
|
||||
import ing.bikeshedengineer.debtpirate.navigation.NavigatorImpl
|
||||
import javax.inject.Singleton
|
||||
|
@ -15,5 +14,5 @@ object NavigatorModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideNavigator(): Navigator = NavigatorImpl(startDestination = Destination.AuthGraph)
|
||||
fun provideNavigator(): Navigator = NavigatorImpl()
|
||||
}
|
|
@ -12,7 +12,7 @@ import javax.inject.Singleton
|
|||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object UseCaseModule {
|
||||
object SingletonUseCaseModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package ing.bikeshedengineer.debtpirate.domain.usecase
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.credentials.CreatePasswordRequest
|
||||
import androidx.credentials.CredentialManager
|
||||
import androidx.credentials.exceptions.CreateCredentialException
|
||||
|
||||
class StoreCredentialsUseCase(val context: Context) {
|
||||
suspend operator fun invoke(username: String, password: String) {
|
||||
val credentialManager = CredentialManager.create(this.context)
|
||||
val createPasswordRequest = CreatePasswordRequest(id = username, password = password)
|
||||
|
||||
try {
|
||||
credentialManager.createCredential(this.context, createPasswordRequest)
|
||||
Log.d("StoreCredentialsUseCase", "Successfully stored login credentials")
|
||||
} catch (err: CreateCredentialException) {
|
||||
// TODO: Throw an error to be displayed as a Toast
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ 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
|
||||
|
@ -15,7 +16,7 @@ interface Navigator {
|
|||
}
|
||||
|
||||
class NavigatorImpl(
|
||||
override val startDestination: Destination,
|
||||
override val startDestination: Destination = Destination.AuthGraph
|
||||
) : Navigator {
|
||||
private val _navigationActions = Channel<NavigationAction>()
|
||||
override val navigationActions = _navigationActions.receiveAsFlow()
|
||||
|
|
Loading…
Add table
Reference in a new issue