After a successful registration, store the user's credentials with credential manager
This commit is contained in:
parent
3a093b60b4
commit
5a437a9813
22 changed files with 216 additions and 220 deletions
|
@ -85,6 +85,7 @@ kapt {
|
|||
dependencies {
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.androidx.biometric)
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.datastore)
|
||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||
|
|
6
app/app/proguard-rules.pro
vendored
6
app/app/proguard-rules.pro
vendored
|
@ -18,4 +18,8 @@
|
|||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
#-renamesourcefileattribute SourceFile
|
||||
-if class androidx.credentials.CredentialManager
|
||||
-keep class androidx.credentials.playservices.** {
|
||||
*;
|
||||
}
|
|
@ -14,6 +14,11 @@
|
|||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.DebtPirate"
|
||||
android:networkSecurityConfig="@xml/network_security_config">
|
||||
|
||||
<meta-data
|
||||
android:name="asset_statements"
|
||||
android:resource="@string/asset_statements" />
|
||||
|
||||
<activity
|
||||
android:name=".app.host.MainActivity"
|
||||
android:exported="true"
|
||||
|
@ -22,7 +27,7 @@
|
|||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
|
|
|
@ -27,6 +27,8 @@ import ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.confirm.Conf
|
|||
import ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.confirm.ConfirmationScreen
|
||||
import ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.login.LoginScreen
|
||||
import ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.register.RegistrationScreen
|
||||
import ing.bikeshedengineer.debtpirate.app.screen.home.presentation.overview.OverviewScreen
|
||||
import ing.bikeshedengineer.debtpirate.domain.usecase.GetStoredTokensUseCase
|
||||
import ing.bikeshedengineer.debtpirate.navigation.Destination
|
||||
import ing.bikeshedengineer.debtpirate.navigation.NavigationAction
|
||||
import ing.bikeshedengineer.debtpirate.navigation.Navigator
|
||||
|
@ -42,6 +44,9 @@ class MainActivity : ComponentActivity() {
|
|||
@Inject
|
||||
lateinit var navigator: Navigator
|
||||
|
||||
@Inject
|
||||
lateinit var getStoredTokens: GetStoredTokensUseCase
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
|
@ -104,6 +109,14 @@ class MainActivity : ComponentActivity() {
|
|||
ConfirmationScreen(ConfirmationData.UserConfirmationData(userId, verificationToken))
|
||||
}
|
||||
}
|
||||
|
||||
navigation<Destination.HomeGraph>(
|
||||
startDestination = Destination.HomeOverview
|
||||
) {
|
||||
composable<Destination.HomeOverview> {
|
||||
OverviewScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import android.annotation.SuppressLint
|
|||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
|
@ -32,8 +31,6 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
@ -44,10 +41,8 @@ import androidx.compose.ui.text.input.KeyboardType
|
|||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.credentials.CredentialManager
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import ing.bikeshedengineer.debtpirate.domain.model.AccountManagerResult
|
||||
import ing.bikeshedengineer.debtpirate.domain.repository.AccountManager
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||
@Composable
|
||||
|
@ -55,23 +50,7 @@ fun LoginScreen(
|
|||
viewModel: LoginScreenViewModel = hiltViewModel<LoginScreenViewModel>()
|
||||
) {
|
||||
val context = LocalContext.current as ComponentActivity
|
||||
val accountManager = remember {
|
||||
AccountManager(context)
|
||||
}
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
LaunchedEffect(true) {
|
||||
coroutineScope.launch {
|
||||
val result = accountManager.getCredentials()
|
||||
when (result) {
|
||||
is AccountManagerResult.FoundCredentials -> {
|
||||
val (emailAddress, password) = result
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
val _credentialManager = CredentialManager.create(context)
|
||||
|
||||
val toastMessages = viewModel.toastMessages.collectAsState("")
|
||||
LaunchedEffect(toastMessages.value) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.login
|
||||
|
||||
import android.util.Log
|
||||
import androidx.credentials.CreatePasswordRequest
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
|
@ -31,9 +32,10 @@ class LoginScreenViewModel @Inject constructor(
|
|||
private val _isEmailAddressPristine = MutableStateFlow(true)
|
||||
private val _emailAddress = MutableStateFlow("")
|
||||
val emailAddress = _emailAddress.asStateFlow()
|
||||
val isValidEmailAddress = _isEmailAddressPristine.combine(_emailAddress.map { it.isNotBlank() }) { isPristine, isValid ->
|
||||
isPristine || isValid
|
||||
}
|
||||
val isValidEmailAddress =
|
||||
_isEmailAddressPristine.combine(_emailAddress.map { it.isNotBlank() }) { isPristine, isValid ->
|
||||
isPristine || isValid
|
||||
}
|
||||
|
||||
private val _isPasswordPristine = MutableStateFlow(true)
|
||||
private val _password = MutableStateFlow("")
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.register
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.util.Log
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
|
@ -32,8 +30,6 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
|
@ -50,37 +46,33 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.credentials.CreatePasswordRequest
|
||||
import androidx.credentials.CredentialManager
|
||||
import androidx.credentials.exceptions.CreateCredentialException
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import ing.bikeshedengineer.debtpirate.domain.model.AccountManagerResult
|
||||
import ing.bikeshedengineer.debtpirate.domain.repository.AccountManager
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||
@Composable()
|
||||
fun RegistrationScreen(
|
||||
viewModel: RegistrationScreenViewModel = hiltViewModel<RegistrationScreenViewModel>()
|
||||
) {
|
||||
val context = LocalContext.current as ComponentActivity
|
||||
val accountManager = remember { AccountManager(context) }
|
||||
val localContext = LocalContext.current
|
||||
val onRegistrationMessage = viewModel.onRegistrationComplete.collectAsState(null)
|
||||
LaunchedEffect(onRegistrationMessage.value) {
|
||||
val message = onRegistrationMessage.value
|
||||
if (message != null) {
|
||||
val (username, password) = message
|
||||
val credentialManager = CredentialManager.create(localContext)
|
||||
val createPasswordRequest = CreatePasswordRequest(id = username, password = password)
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
LaunchedEffect(true) {
|
||||
coroutineScope.launch {
|
||||
viewModel.onRegistrationComplete.collect { credentials ->
|
||||
val (emailAddress, password) = credentials
|
||||
val result = accountManager.storeCredentials(emailAddress, password)
|
||||
|
||||
if (result !is AccountManagerResult.Success) {
|
||||
Log.i(
|
||||
"RegistrationScreen",
|
||||
"Not able to store credentials in CredentialManager"
|
||||
)
|
||||
}
|
||||
|
||||
viewModel.onAction(RegistrationScreenAction.ResetFields)
|
||||
viewModel.navigateToConfirmationScreen(emailAddress)
|
||||
try {
|
||||
credentialManager.createCredential(localContext, createPasswordRequest)
|
||||
Log.d("RegistrationScreen", "Successfully stored login credentials")
|
||||
} catch (err: CreateCredentialException) {
|
||||
// TODO: Display a toast...
|
||||
}
|
||||
|
||||
viewModel.navigateToConfirmationScreen(username)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.register
|
||||
|
||||
import androidx.credentials.CreatePasswordRequest
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
|
@ -32,11 +33,7 @@ class RegistrationScreenViewModel @Inject constructor(
|
|||
|
||||
fun navigateToConfirmationScreen(emailAddress: String) {
|
||||
viewModelScope.launch {
|
||||
navigator.navigate(Destination.AuthRegistrationConfirmation(emailAddress)) {
|
||||
popUpTo(Destination.AuthLogin) {
|
||||
inclusive = true
|
||||
}
|
||||
}
|
||||
navigator.navigate(Destination.AuthRegistrationConfirmation(emailAddress))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,7 +115,7 @@ class RegistrationScreenViewModel @Inject constructor(
|
|||
val result =
|
||||
submitAccountRegistrationRequest(emailAddress, name, confirmPassword)
|
||||
|
||||
val (userId, expiresAt, sessionToken) = result
|
||||
val (_, expiresAt, sessionToken) = result
|
||||
|
||||
updateStoreData(
|
||||
userId = result.userId,
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
package ing.bikeshedengineer.debtpirate.app.screen.home.presentation.overview
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable()
|
||||
fun OverviewScreen() {
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.padding(16.dp)
|
||||
)
|
||||
{
|
||||
OverviewComponent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun OverviewComponent() {
|
||||
Text("Hello, world!")
|
||||
}
|
|
@ -1,10 +1,25 @@
|
|||
package ing.bikeshedengineer.debtpirate.data.repository
|
||||
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.ApiResponse
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.endpoint.AuthEndpoint
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginPostRequest
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginPostResponse
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import retrofit2.Response
|
||||
import retrofit2.Retrofit
|
||||
|
||||
interface AuthRepository {
|
||||
suspend fun submitAuthLoginRequest(credentials: AuthLoginPostRequest): Response<ApiResponse<AuthLoginPostResponse>>
|
||||
}
|
||||
|
||||
class AuthRepositoryImpl(private val httpClient: Retrofit) : AuthRepository {
|
||||
private val authEndpoint: AuthEndpoint = this.httpClient.create(AuthEndpoint::class.java)
|
||||
|
||||
override suspend fun submitAuthLoginRequest(credentials: AuthLoginPostRequest): Response<ApiResponse<AuthLoginPostResponse>> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val response = authEndpoint.submitAuthLoginRequest(credentials)
|
||||
return@withContext response
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,59 @@
|
|||
package ing.bikeshedengineer.debtpirate.data.repository
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.ApiResponse
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.endpoint.UserEndpoint
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserCreatePostRequest
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserCreatePostResponse
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserVerificationGetRequest
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserVerificationGetResponse
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import retrofit2.Retrofit
|
||||
|
||||
interface UserRepository {
|
||||
suspend fun createUserPostRequest(request: UserCreatePostRequest): UserCreatePostResponse
|
||||
suspend fun verifyUserGetRequest(request: UserVerificationGetRequest): UserVerificationGetResponse
|
||||
}
|
||||
|
||||
class UserRepositoryImpl(httpClient: Retrofit) : UserRepository {
|
||||
private val userEndpoint: UserEndpoint = httpClient.create(UserEndpoint::class.java)
|
||||
|
||||
override suspend fun createUserPostRequest(request: UserCreatePostRequest): UserCreatePostResponse {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val response = userEndpoint.createUserPostRequest(request)
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val body = response.body()
|
||||
return@withContext body!!.data!!
|
||||
} else {
|
||||
val gson = Gson()
|
||||
val errorType = object : TypeToken<ApiResponse<Unit>>() {}.type
|
||||
val body =
|
||||
gson.fromJson<ApiResponse<Unit>>(response.errorBody()!!.charStream(), errorType)
|
||||
|
||||
throw Throwable(body!!.error!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun verifyUserGetRequest(request: UserVerificationGetRequest): UserVerificationGetResponse {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val (userId, verificationToken) = request
|
||||
val response = userEndpoint.userVerificationGetRequest(userId, verificationToken)
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val body = response.body()
|
||||
return@withContext body!!.data!!
|
||||
} else {
|
||||
val gson = Gson()
|
||||
val errorType = object : TypeToken<ApiResponse<Unit>>() {}.type
|
||||
val body =
|
||||
gson.fromJson<ApiResponse<Unit>>(response.errorBody()!!.charStream(), errorType)
|
||||
|
||||
throw Throwable(body!!.error!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ import dagger.Provides
|
|||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import ing.bikeshedengineer.debtpirate.AppDataStore
|
||||
import ing.bikeshedengineer.debtpirate.domain.usecase.GetStoredTokensUseCase
|
||||
import ing.bikeshedengineer.debtpirate.domain.usecase.UpdateStoreDataUseCase
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
@ -16,4 +17,8 @@ object UseCaseModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideUpdateStoreDataUseCase(store: DataStore<AppDataStore>) = UpdateStoreDataUseCase(store)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetStoredTokensUseCase(store: DataStore<AppDataStore>) = GetStoredTokensUseCase(store)
|
||||
}
|
|
@ -6,7 +6,7 @@ import dagger.hilt.InstallIn
|
|||
import dagger.hilt.android.components.ViewModelComponent
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
import ing.bikeshedengineer.debtpirate.data.repository.AuthRepository
|
||||
import ing.bikeshedengineer.debtpirate.domain.repository.AuthRepositoryImpl
|
||||
import ing.bikeshedengineer.debtpirate.data.repository.AuthRepositoryImpl
|
||||
import retrofit2.Retrofit
|
||||
|
||||
@Module
|
||||
|
|
|
@ -6,7 +6,7 @@ import dagger.hilt.InstallIn
|
|||
import dagger.hilt.android.components.ViewModelComponent
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
import ing.bikeshedengineer.debtpirate.data.repository.UserRepository
|
||||
import ing.bikeshedengineer.debtpirate.domain.repository.UserRepositoryImpl
|
||||
import ing.bikeshedengineer.debtpirate.data.repository.UserRepositoryImpl
|
||||
import retrofit2.Retrofit
|
||||
|
||||
@Module
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
package ing.bikeshedengineer.debtpirate.domain.repository
|
||||
|
||||
import android.app.Activity
|
||||
import android.util.Log
|
||||
import androidx.credentials.CreatePasswordRequest
|
||||
import androidx.credentials.CredentialManager
|
||||
import androidx.credentials.GetCredentialRequest
|
||||
import androidx.credentials.GetPasswordOption
|
||||
import androidx.credentials.PasswordCredential
|
||||
import androidx.credentials.exceptions.CreateCredentialCancellationException
|
||||
import androidx.credentials.exceptions.CreateCredentialException
|
||||
import androidx.credentials.exceptions.CreateCredentialNoCreateOptionException
|
||||
import ing.bikeshedengineer.debtpirate.domain.model.AccountManagerResult
|
||||
|
||||
class AccountManager(private val context: Activity) {
|
||||
private val credentialManager = CredentialManager.create(context)
|
||||
|
||||
suspend fun storeCredentials(
|
||||
emailAddress: String,
|
||||
password: String
|
||||
): AccountManagerResult {
|
||||
return try {
|
||||
credentialManager.createCredential(
|
||||
context, request = CreatePasswordRequest(
|
||||
id = emailAddress,
|
||||
password
|
||||
)
|
||||
)
|
||||
|
||||
AccountManagerResult.Success
|
||||
} catch (err: CreateCredentialNoCreateOptionException) {
|
||||
Log.w(
|
||||
"AccountManager",
|
||||
"Cannot store credentials; a Google account isn't associated with this device"
|
||||
)
|
||||
|
||||
AccountManagerResult.Unavailable
|
||||
} catch (_: CreateCredentialCancellationException) {
|
||||
AccountManagerResult.Canceled
|
||||
} catch (err: CreateCredentialException) {
|
||||
err.printStackTrace()
|
||||
Log.i(
|
||||
"AccountManager",
|
||||
"Unable to store credentials: ${err.message}"
|
||||
)
|
||||
|
||||
AccountManagerResult.Failure
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getCredentials(): AccountManagerResult {
|
||||
return try {
|
||||
val result = credentialManager.getCredential(
|
||||
context, request = GetCredentialRequest(
|
||||
credentialOptions = listOf(GetPasswordOption(isAutoSelectAllowed = true))
|
||||
)
|
||||
)
|
||||
|
||||
val credentials = result.credential
|
||||
when (credentials) {
|
||||
is PasswordCredential -> {
|
||||
val emailAddress = credentials.id
|
||||
val password = credentials.password
|
||||
|
||||
AccountManagerResult.FoundCredentials(emailAddress, password)
|
||||
}
|
||||
|
||||
else -> AccountManagerResult.Invalid
|
||||
}
|
||||
} catch (err: Exception) {
|
||||
AccountManagerResult.Failure
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package ing.bikeshedengineer.debtpirate.domain.repository
|
||||
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.ApiResponse
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.endpoint.AuthEndpoint
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginPostRequest
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginPostResponse
|
||||
import ing.bikeshedengineer.debtpirate.data.repository.AuthRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import retrofit2.Response
|
||||
import retrofit2.Retrofit
|
||||
|
||||
class AuthRepositoryImpl(private val httpClient: Retrofit) : AuthRepository {
|
||||
private val authEndpoint: AuthEndpoint = this.httpClient.create(AuthEndpoint::class.java)
|
||||
|
||||
override suspend fun submitAuthLoginRequest(credentials: AuthLoginPostRequest): Response<ApiResponse<AuthLoginPostResponse>> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val response = authEndpoint.submitAuthLoginRequest(credentials)
|
||||
return@withContext response
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
package ing.bikeshedengineer.debtpirate.domain.repository
|
||||
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.ApiResponse
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.endpoint.UserEndpoint
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserCreatePostRequest
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserCreatePostResponse
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserVerificationGetRequest
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserVerificationGetResponse
|
||||
import ing.bikeshedengineer.debtpirate.data.repository.UserRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import retrofit2.Retrofit
|
||||
|
||||
class UserRepositoryImpl(httpClient: Retrofit) : UserRepository {
|
||||
private val userEndpoint: UserEndpoint = httpClient.create(UserEndpoint::class.java)
|
||||
|
||||
override suspend fun createUserPostRequest(request: UserCreatePostRequest): UserCreatePostResponse {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val response = userEndpoint.createUserPostRequest(request)
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val body = response.body()
|
||||
return@withContext body!!.data!!
|
||||
} else {
|
||||
val gson = Gson()
|
||||
val errorType = object : TypeToken<ApiResponse<Unit>>() {}.type
|
||||
val body =
|
||||
gson.fromJson<ApiResponse<Unit>>(response.errorBody()!!.charStream(), errorType)
|
||||
|
||||
throw Throwable(body!!.error!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun verifyUserGetRequest(request: UserVerificationGetRequest): UserVerificationGetResponse {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val (userId, verificationToken) = request
|
||||
val response = userEndpoint.userVerificationGetRequest(userId, verificationToken)
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val body = response.body()
|
||||
return@withContext body!!.data!!
|
||||
} else {
|
||||
val gson = Gson()
|
||||
val errorType = object : TypeToken<ApiResponse<Unit>>() {}.type
|
||||
val body =
|
||||
gson.fromJson<ApiResponse<Unit>>(response.errorBody()!!.charStream(), errorType)
|
||||
|
||||
throw Throwable(body!!.error!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package ing.bikeshedengineer.debtpirate.domain.usecase
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import ing.bikeshedengineer.debtpirate.AppDataStore
|
||||
import ing.bikeshedengineer.debtpirate.domain.model.Token
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
class GetStoredTokensUseCase(private val store: DataStore<AppDataStore>) {
|
||||
operator fun invoke(): Flow<Pair<Token, Token?>?> {
|
||||
return store.data.map { store ->
|
||||
if (store.isInitialized) {
|
||||
val now = ZonedDateTime.now()
|
||||
val authTokenExpiration = ZonedDateTime.ofInstant(Instant.ofEpochSecond(store.authToken.expiresAt), ZoneOffset.UTC)
|
||||
if (authTokenExpiration.isBefore(now)) {
|
||||
return@map null
|
||||
}
|
||||
|
||||
val sessionTokenExpiration = ZonedDateTime.ofInstant(Instant.ofEpochSecond(store.sessionToken.expiresAt), ZoneOffset.UTC)
|
||||
return@map Pair(
|
||||
Token(
|
||||
store.authToken.token,
|
||||
authTokenExpiration
|
||||
),
|
||||
if (sessionTokenExpiration.isBefore(now)) {
|
||||
null
|
||||
} else {
|
||||
Token(
|
||||
store.sessionToken.token,
|
||||
sessionTokenExpiration
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
return@map null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ import androidx.datastore.core.DataStore
|
|||
import ing.bikeshedengineer.debtpirate.AppDataStore
|
||||
import java.time.OffsetDateTime
|
||||
|
||||
class UpdateStoreDataUseCase(val store: DataStore<AppDataStore>) {
|
||||
class UpdateStoreDataUseCase(private val store: DataStore<AppDataStore>) {
|
||||
suspend operator fun invoke(
|
||||
userId: Int? = null,
|
||||
authToken: String? = null,
|
||||
|
|
|
@ -24,4 +24,11 @@ sealed interface Destination {
|
|||
val userId: Int,
|
||||
val verificationToken: String
|
||||
) : Destination
|
||||
|
||||
/* Home Destinations */
|
||||
@Serializable
|
||||
data object HomeGraph : Destination
|
||||
|
||||
@Serializable
|
||||
data object HomeOverview : Destination
|
||||
}
|
|
@ -1,3 +1,8 @@
|
|||
<resources>
|
||||
<string name="app_name">Debt Pirate</string>
|
||||
<string name="asset_statements" translatable="false">
|
||||
[{
|
||||
\"include\": \"https://debtpirate.app/.well-known/assetlinks.json\"
|
||||
}]
|
||||
</string>
|
||||
</resources>
|
|
@ -2,12 +2,13 @@
|
|||
activityCompose = "1.9.3"
|
||||
agp = "8.7.3"
|
||||
appcompat = "1.7.0"
|
||||
composeBom = "2024.11.00"
|
||||
biometric = "1.4.0-alpha02"
|
||||
composeBom = "2024.12.01"
|
||||
coreKtx = "1.15.0"
|
||||
datastore = "1.1.1"
|
||||
espressoCore = "3.6.1"
|
||||
hilt = "2.51.1"
|
||||
iconsExtended = "1.7.5"
|
||||
iconsExtended = "1.7.6"
|
||||
junit = "4.13.2"
|
||||
junitVersion = "1.2.1"
|
||||
kotlin = "2.0.10"
|
||||
|
@ -17,19 +18,20 @@ lifecycleRuntimeKtx = "2.8.7"
|
|||
lifecycleViewModelKtx = "2.8.7"
|
||||
lifecycleViewmodelCompose = "2.8.7"
|
||||
material = "1.12.0"
|
||||
material3 = "1.4.0-alpha04"
|
||||
navigation = "2.8.4"
|
||||
material3 = "1.4.0-alpha05"
|
||||
navigation = "2.8.5"
|
||||
protobuf = "0.9.4"
|
||||
protoLite = "3.21.11"
|
||||
okhttp = "4.10.0"
|
||||
retrofit = "2.9.0"
|
||||
hiltNavigationCompose = "1.2.0"
|
||||
fonts = "1.7.5"
|
||||
fonts = "1.7.6"
|
||||
credentialManager = "1.5.0-beta01"
|
||||
|
||||
[libraries]
|
||||
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
||||
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||
androidx-biometric = { group = "androidx.biometric", name = "biometric", version.ref = "biometric" }
|
||||
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
androidx-credentials-core = { group = "androidx.credentials", name = "credentials", version.ref = "credentialManager" }
|
||||
|
|
Loading…
Add table
Reference in a new issue