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.auth.presentation.register.RegistrationScreenViewModel
|
||||||
import ing.bikeshedengineer.debtpirate.app.screen.home.presentation.overview.OverviewScreen
|
import ing.bikeshedengineer.debtpirate.app.screen.home.presentation.overview.OverviewScreen
|
||||||
import ing.bikeshedengineer.debtpirate.domain.usecase.GetStoredTokensUseCase
|
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.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
|
||||||
|
@ -51,6 +52,9 @@ class MainActivity : ComponentActivity() {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var getStoredTokens: GetStoredTokensUseCase
|
lateinit var getStoredTokens: GetStoredTokensUseCase
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var storeCredentials: StoreCredentialsUseCase
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
@ -60,6 +64,7 @@ class MainActivity : ComponentActivity() {
|
||||||
setContent {
|
setContent {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
|
|
||||||
|
|
||||||
ObserveAsEvents(navigator.navigationActions) { action ->
|
ObserveAsEvents(navigator.navigationActions) { action ->
|
||||||
when (action) {
|
when (action) {
|
||||||
is NavigationAction.Navigate -> {
|
is NavigationAction.Navigate -> {
|
||||||
|
@ -102,16 +107,18 @@ class MainActivity : ComponentActivity() {
|
||||||
) {
|
) {
|
||||||
composable<Destination.AuthLogin> {
|
composable<Destination.AuthLogin> {
|
||||||
val viewModel = hiltViewModel<LoginScreenViewModel>()
|
val viewModel = hiltViewModel<LoginScreenViewModel>()
|
||||||
|
val toastMessages = viewModel.toastMessages.collectAsState("")
|
||||||
|
val storeCredentialMessages = viewModel.storeCredentialsMessages.collectAsState(null);
|
||||||
|
|
||||||
LoginScreen(
|
LoginScreen(
|
||||||
emailAddress = viewModel.emailAddress.collectAsState(""),
|
emailAddress = viewModel.emailAddress.collectAsState(""),
|
||||||
isEmailAddressValid = viewModel.isEmailAddressValid.collectAsState(true),
|
isEmailAddressValid = viewModel.isEmailAddressValid.collectAsState(true),
|
||||||
password = viewModel.password.collectAsState(""),
|
password = viewModel.password.collectAsState(""),
|
||||||
isPasswordValid = viewModel.isPasswordValid.collectAsState(true),
|
isPasswordValid = viewModel.isPasswordValid.collectAsState(true),
|
||||||
toastMessages = viewModel.toastMessages.collectAsState(""),
|
toastMessages = toastMessages,
|
||||||
onAction = viewModel::onAction,
|
|
||||||
handleCredentialManagerSignIn = viewModel::handleCredentialManagerSignIn,
|
handleCredentialManagerSignIn = viewModel::handleCredentialManagerSignIn,
|
||||||
onRegisterButtonClick = viewModel::onRegisterButtonClick
|
onRegisterButtonClick = viewModel::onRegisterButtonClick,
|
||||||
|
onAction = viewModel::onAction,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable<Destination.AuthRegistration> {
|
composable<Destination.AuthRegistration> {
|
||||||
|
|
|
@ -46,7 +46,6 @@ import androidx.credentials.GetCredentialResponse
|
||||||
import androidx.credentials.GetPasswordOption
|
import androidx.credentials.GetPasswordOption
|
||||||
import androidx.credentials.exceptions.GetCredentialException
|
import androidx.credentials.exceptions.GetCredentialException
|
||||||
import androidx.credentials.exceptions.NoCredentialException
|
import androidx.credentials.exceptions.NoCredentialException
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
|
||||||
|
|
||||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -56,9 +55,9 @@ fun LoginScreen(
|
||||||
password: State<String>,
|
password: State<String>,
|
||||||
isPasswordValid: State<Boolean>,
|
isPasswordValid: State<Boolean>,
|
||||||
toastMessages: State<String>,
|
toastMessages: State<String>,
|
||||||
onAction: (LoginScreenStateAction) -> Unit,
|
|
||||||
handleCredentialManagerSignIn: (GetCredentialResponse) -> Unit,
|
handleCredentialManagerSignIn: (GetCredentialResponse) -> Unit,
|
||||||
onRegisterButtonClick: () -> Unit,
|
onRegisterButtonClick: () -> Unit,
|
||||||
|
onAction: (LoginScreenStateAction) -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalActivity.current!!
|
val context = LocalActivity.current!!
|
||||||
val credentialManager = CredentialManager.create(context)
|
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 ing.bikeshedengineer.debtpirate.navigation.Navigator
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
@ -51,6 +51,22 @@ class LoginScreenViewModel @Inject constructor(
|
||||||
val isEmailAddressValid = _state.map { it.isEmailAddressValid }
|
val isEmailAddressValid = _state.map { it.isEmailAddressValid }
|
||||||
val isPasswordValid = _state.map { it.isPasswordValid }
|
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 {
|
init {
|
||||||
_state.distinctUntilChangedBy { it.emailAddress }
|
_state.distinctUntilChangedBy { it.emailAddress }
|
||||||
.map { it.emailAddress.isNotBlank() && Patterns.EMAIL_ADDRESS.matcher(it.emailAddress).matches() }
|
.map { it.emailAddress.isNotBlank() && Patterns.EMAIL_ADDRESS.matcher(it.emailAddress).matches() }
|
||||||
|
@ -77,9 +93,6 @@ class LoginScreenViewModel @Inject constructor(
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _toastMessages = MutableSharedFlow<String>()
|
|
||||||
val toastMessages = _toastMessages.asSharedFlow()
|
|
||||||
|
|
||||||
fun onAction(action: LoginScreenStateAction) {
|
fun onAction(action: LoginScreenStateAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is LoginScreenStateAction.UpdateEmailAddress -> {
|
is LoginScreenStateAction.UpdateEmailAddress -> {
|
||||||
|
@ -96,7 +109,7 @@ class LoginScreenViewModel @Inject constructor(
|
||||||
|
|
||||||
is LoginScreenStateAction.SubmitLoginRequest -> {
|
is LoginScreenStateAction.SubmitLoginRequest -> {
|
||||||
viewModelScope.launch {
|
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 {
|
try {
|
||||||
val (userId, auth, session) = submitLoginCredentials(emailAddress, password)
|
val (userId, auth, session) = submitLoginCredentials(_state.value.emailAddress, _state.value.password)
|
||||||
updateStoreData(
|
updateStoreData(
|
||||||
userId = userId,
|
userId = userId,
|
||||||
authToken = auth.token,
|
authToken = auth.token,
|
||||||
|
@ -131,15 +144,15 @@ class LoginScreenViewModel @Inject constructor(
|
||||||
} catch (err: Exception) {
|
} catch (err: Exception) {
|
||||||
when (err) {
|
when (err) {
|
||||||
is InvalidCredentialsException -> {
|
is InvalidCredentialsException -> {
|
||||||
_toastMessages.emit("Invalid Email Address or Password")
|
_messages.emit(LoginScreenMessage.Toast("Invalid Email Address or Password"))
|
||||||
}
|
}
|
||||||
|
|
||||||
is UserNotFoundException -> {
|
is UserNotFoundException -> {
|
||||||
_toastMessages.emit("User Not Found")
|
_messages.emit(LoginScreenMessage.Toast("User Not Found"))
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
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.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import ing.bikeshedengineer.debtpirate.navigation.Destination
|
|
||||||
import ing.bikeshedengineer.debtpirate.navigation.Navigator
|
import ing.bikeshedengineer.debtpirate.navigation.Navigator
|
||||||
import ing.bikeshedengineer.debtpirate.navigation.NavigatorImpl
|
import ing.bikeshedengineer.debtpirate.navigation.NavigatorImpl
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
@ -15,5 +14,5 @@ object NavigatorModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideNavigator(): Navigator = NavigatorImpl(startDestination = Destination.AuthGraph)
|
fun provideNavigator(): Navigator = NavigatorImpl()
|
||||||
}
|
}
|
|
@ -12,7 +12,7 @@ import javax.inject.Singleton
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
object UseCaseModule {
|
object SingletonUseCaseModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@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.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
|
||||||
|
@ -15,7 +16,7 @@ interface Navigator {
|
||||||
}
|
}
|
||||||
|
|
||||||
class NavigatorImpl(
|
class NavigatorImpl(
|
||||||
override val startDestination: Destination,
|
override val startDestination: Destination = Destination.AuthGraph
|
||||||
) : Navigator {
|
) : Navigator {
|
||||||
private val _navigationActions = Channel<NavigationAction>()
|
private val _navigationActions = Channel<NavigationAction>()
|
||||||
override val navigationActions = _navigationActions.receiveAsFlow()
|
override val navigationActions = _navigationActions.receiveAsFlow()
|
||||||
|
|
Loading…
Add table
Reference in a new issue