diff --git a/app/app/build.gradle.kts b/app/app/build.gradle.kts index 44f3719..0fc7438 100644 --- a/app/app/build.gradle.kts +++ b/app/app/build.gradle.kts @@ -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) diff --git a/app/app/proguard-rules.pro b/app/app/proguard-rules.pro index 481bb43..b8e2ed9 100644 --- a/app/app/proguard-rules.pro +++ b/app/app/proguard-rules.pro @@ -18,4 +18,8 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile +-if class androidx.credentials.CredentialManager +-keep class androidx.credentials.playservices.** { + *; +} \ No newline at end of file diff --git a/app/app/src/main/AndroidManifest.xml b/app/app/src/main/AndroidManifest.xml index 913b745..bc5b292 100644 --- a/app/app/src/main/AndroidManifest.xml +++ b/app/app/src/main/AndroidManifest.xml @@ -14,6 +14,11 @@ android:supportsRtl="true" android:theme="@style/Theme.DebtPirate" android:networkSecurityConfig="@xml/network_security_config"> + + + - + diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/host/MainActivity.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/host/MainActivity.kt index 059baee..d0ee4c3 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/host/MainActivity.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/host/MainActivity.kt @@ -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( + startDestination = Destination.HomeOverview + ) { + composable { + OverviewScreen() + } + } } } } diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/login/LoginScreen.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/login/LoginScreen.kt index 6c9e666..4d56200 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/login/LoginScreen.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/login/LoginScreen.kt @@ -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() ) { 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) { diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/login/LoginScreenViewModel.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/login/LoginScreenViewModel.kt index 6db3d9b..d0ee3b2 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/login/LoginScreenViewModel.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/login/LoginScreenViewModel.kt @@ -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("") diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/register/RegistrationScreen.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/register/RegistrationScreen.kt index fc98d8b..7a6e1c3 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/register/RegistrationScreen.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/register/RegistrationScreen.kt @@ -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() ) { - 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) } } diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/register/RegistrationScreenViewModel.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/register/RegistrationScreenViewModel.kt index 7faf874..eb28056 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/register/RegistrationScreenViewModel.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/register/RegistrationScreenViewModel.kt @@ -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, diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/home/presentation/overview/OverviewScreen.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/home/presentation/overview/OverviewScreen.kt new file mode 100644 index 0000000..ea5bd2b --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/home/presentation/overview/OverviewScreen.kt @@ -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!") +} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/repository/AuthRepository.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/repository/AuthRepository.kt index c851a3e..0e4ff3c 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/repository/AuthRepository.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/repository/AuthRepository.kt @@ -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> +} + +class AuthRepositoryImpl(private val httpClient: Retrofit) : AuthRepository { + private val authEndpoint: AuthEndpoint = this.httpClient.create(AuthEndpoint::class.java) + + override suspend fun submitAuthLoginRequest(credentials: AuthLoginPostRequest): Response> { + return withContext(Dispatchers.IO) { + val response = authEndpoint.submitAuthLoginRequest(credentials) + return@withContext response + } + } } \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/repository/UserRepository.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/repository/UserRepository.kt index 4e15104..29ae64f 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/repository/UserRepository.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/repository/UserRepository.kt @@ -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>() {}.type + val body = + gson.fromJson>(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>() {}.type + val body = + gson.fromJson>(response.errorBody()!!.charStream(), errorType) + + throw Throwable(body!!.error!!) + } + } + } } \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/UseCaseModule.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/UseCaseModule.kt index b8ce299..fd2b582 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/UseCaseModule.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/UseCaseModule.kt @@ -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) = UpdateStoreDataUseCase(store) + + @Provides + @Singleton + fun provideGetStoredTokensUseCase(store: DataStore) = GetStoredTokensUseCase(store) } \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/repository/AuthRepositoryModule.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/repository/AuthRepositoryModule.kt index 0a6b8cf..340f24d 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/repository/AuthRepositoryModule.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/repository/AuthRepositoryModule.kt @@ -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 diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/repository/UserRepositoryModule.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/repository/UserRepositoryModule.kt index 4d47321..29be89a 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/repository/UserRepositoryModule.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/repository/UserRepositoryModule.kt @@ -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 diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/repository/AccountManager.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/repository/AccountManager.kt deleted file mode 100644 index 0ebc15e..0000000 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/repository/AccountManager.kt +++ /dev/null @@ -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 - } - } -} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/repository/AuthRepositoryImpl.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/repository/AuthRepositoryImpl.kt deleted file mode 100644 index e8faef5..0000000 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/repository/AuthRepositoryImpl.kt +++ /dev/null @@ -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> { - return withContext(Dispatchers.IO) { - val response = authEndpoint.submitAuthLoginRequest(credentials) - return@withContext response - } - } -} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/repository/UserRepositoryImpl.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/repository/UserRepositoryImpl.kt deleted file mode 100644 index 4cc6977..0000000 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/repository/UserRepositoryImpl.kt +++ /dev/null @@ -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>() {}.type - val body = - gson.fromJson>(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>() {}.type - val body = - gson.fromJson>(response.errorBody()!!.charStream(), errorType) - - throw Throwable(body!!.error!!) - } - } - } -} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/usecase/GetStoredTokensUseCase.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/usecase/GetStoredTokensUseCase.kt new file mode 100644 index 0000000..3467575 --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/usecase/GetStoredTokensUseCase.kt @@ -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) { + operator fun invoke(): Flow?> { + 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 + } + } + } +} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/usecase/UpdateStoreDataUseCase.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/usecase/UpdateStoreDataUseCase.kt index 913eafe..5dd8a43 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/usecase/UpdateStoreDataUseCase.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/usecase/UpdateStoreDataUseCase.kt @@ -4,7 +4,7 @@ import androidx.datastore.core.DataStore import ing.bikeshedengineer.debtpirate.AppDataStore import java.time.OffsetDateTime -class UpdateStoreDataUseCase(val store: DataStore) { +class UpdateStoreDataUseCase(private val store: DataStore) { suspend operator fun invoke( userId: Int? = null, authToken: String? = null, diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/navigation/Destination.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/navigation/Destination.kt index 1fce751..d8f8981 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/navigation/Destination.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/navigation/Destination.kt @@ -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 } \ No newline at end of file diff --git a/app/app/src/main/res/values/strings.xml b/app/app/src/main/res/values/strings.xml index 4be604b..c51740c 100644 --- a/app/app/src/main/res/values/strings.xml +++ b/app/app/src/main/res/values/strings.xml @@ -1,3 +1,8 @@ Debt Pirate + + [{ + \"include\": \"https://debtpirate.app/.well-known/assetlinks.json\" + }] + \ No newline at end of file diff --git a/app/gradle/libs.versions.toml b/app/gradle/libs.versions.toml index 94287f7..9eaac3b 100644 --- a/app/gradle/libs.versions.toml +++ b/app/gradle/libs.versions.toml @@ -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" }