Store user credentials after registration
This commit is contained in:
parent
da1cebe02d
commit
a343f2e2a0
12 changed files with 209 additions and 87 deletions
|
@ -62,7 +62,7 @@ android {
|
|||
)
|
||||
}
|
||||
debug {
|
||||
applicationIdSuffix = ".d"
|
||||
applicationIdSuffix = ".dev"
|
||||
buildConfigField("String", "API_BASE_URL", "\"http://10.0.2.2:42069\"")
|
||||
}
|
||||
}
|
||||
|
@ -110,6 +110,8 @@ dependencies {
|
|||
kapt(libs.hilt.kapt)
|
||||
implementation(libs.hilt.compose)
|
||||
implementation(libs.google.fonts)
|
||||
implementation(libs.androidx.credentials.core)
|
||||
implementation(libs.androidx.credentials.compat)
|
||||
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
|
|
|
@ -2,6 +2,7 @@ package ing.bikeshedengineer.debtpirate.app.host
|
|||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.SystemBarStyle
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.animation.AnimatedContentTransitionScope
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.login
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
@ -28,10 +29,10 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
|
@ -40,13 +41,18 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
|
|||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import ing.bikeshedengineer.debtpirate.R
|
||||
import ing.bikeshedengineer.debtpirate.domain.repository.AccountManager
|
||||
|
||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||
@Composable
|
||||
fun LoginScreen(
|
||||
viewModel: LoginScreenViewModel = hiltViewModel<LoginScreenViewModel>()
|
||||
) {
|
||||
val context = LocalContext.current as ComponentActivity
|
||||
val accountManager = remember {
|
||||
AccountManager(context)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
|
@ -67,13 +73,32 @@ fun LoginScreen(
|
|||
|
||||
LoginComponent(
|
||||
emailAddress = emailAddress,
|
||||
onUpdateEmailAddress = viewModel::updateEmailAddress,
|
||||
onUpdateEmailAddress = {
|
||||
viewModel.updateState(
|
||||
LoginScreenStateAction.UpdateEmailAddress(
|
||||
it
|
||||
)
|
||||
)
|
||||
},
|
||||
password = password,
|
||||
onUpdatePassword = viewModel::updatePassword,
|
||||
onUpdatePassword = {
|
||||
viewModel.updateState(
|
||||
LoginScreenStateAction.UpdatePassword(
|
||||
it
|
||||
)
|
||||
)
|
||||
},
|
||||
submitLoginRequest = viewModel::submitLoginRequest
|
||||
)
|
||||
|
||||
Separator(modifier = Modifier.padding(PaddingValues(top = 24.dp, bottom = 24.dp)))
|
||||
Separator(
|
||||
modifier = Modifier.padding(
|
||||
PaddingValues(
|
||||
top = 24.dp,
|
||||
bottom = 24.dp
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
RegisterButton(viewModel = viewModel)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.login
|
||||
|
||||
sealed interface LoginScreenStateAction {
|
||||
data class UpdateEmailAddress(val emailAddress: String) : LoginScreenStateAction
|
||||
data class UpdatePassword(val password: String) : LoginScreenStateAction
|
||||
}
|
|
@ -49,12 +49,16 @@ class LoginScreenViewModel @Inject constructor(
|
|||
private val _password = MutableStateFlow("")
|
||||
val password = _password.asStateFlow()
|
||||
|
||||
fun updateEmailAddress(emailAddress: String) {
|
||||
_emailAddress.value = emailAddress
|
||||
}
|
||||
fun updateState(action: LoginScreenStateAction) {
|
||||
when (action) {
|
||||
is LoginScreenStateAction.UpdateEmailAddress -> {
|
||||
_emailAddress.value = action.emailAddress
|
||||
}
|
||||
|
||||
fun updatePassword(password: String) {
|
||||
_password.value = password
|
||||
is LoginScreenStateAction.UpdatePassword -> {
|
||||
_password.value = action.password
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun submitLoginRequest() {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
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
|
||||
|
@ -26,12 +28,15 @@ import androidx.compose.material3.TopAppBar
|
|||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
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
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
|
@ -45,14 +50,41 @@ import androidx.compose.ui.text.withStyle
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import ing.bikeshedengineer.debtpirate.R
|
||||
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 scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
fun RegistrationScreen(
|
||||
viewModel: RegistrationScreenViewModel = hiltViewModel<RegistrationScreenViewModel>()
|
||||
) {
|
||||
val context = LocalContext.current as ComponentActivity
|
||||
val accountManager = remember { AccountManager(context) }
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
LaunchedEffect(true) {
|
||||
coroutineScope.launch {
|
||||
viewModel.onRegistrationComplete.collect { credentials ->
|
||||
val (emailAddress, password) = credentials
|
||||
val result = accountManager.storeCredentials(emailAddress, password)
|
||||
|
||||
when (result) {
|
||||
is AccountManagerResult.Unavailable -> {
|
||||
viewModel.navigateUp()
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
||||
}
|
||||
}
|
||||
Log.d("RegistrationScreen", "$result")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
Scaffold(
|
||||
topBar = { RegistrationTopAppBar(onNavigateUp = viewModel::navigateUp) },
|
||||
modifier = Modifier
|
||||
|
@ -64,40 +96,8 @@ fun RegistrationScreen(viewModel: RegistrationScreenViewModel = hiltViewModel<Re
|
|||
.padding(innerPadding)
|
||||
.padding(16.dp)
|
||||
) {
|
||||
val emailAddress = viewModel.emailAddress.collectAsState()
|
||||
val emailAddressError = viewModel.emailAddressError.collectAsState()
|
||||
val isInvalidEmailAddress = viewModel.isInvalidEmailAddress.collectAsState(false)
|
||||
|
||||
val name = viewModel.name.collectAsState()
|
||||
val nameError = viewModel.nameError.collectAsState()
|
||||
val isInvalidName = viewModel.isInvalidName.collectAsState(false)
|
||||
|
||||
val password = viewModel.password.collectAsState()
|
||||
val passwordError = viewModel.passwordError.collectAsState()
|
||||
val isInvalidPassword = viewModel.isInvalidPassword.collectAsState(false)
|
||||
|
||||
val confirmPassword = viewModel.confirmPassword.collectAsState()
|
||||
val confirmPasswordError = viewModel.confirmPasswordError.collectAsState()
|
||||
val isInvalidConfirmPassword = viewModel.isInvalidConfirmPassword.collectAsState(false)
|
||||
|
||||
RegistrationComponent(
|
||||
emailAddress = emailAddress,
|
||||
emailAddressError = emailAddressError,
|
||||
isInvalidEmailAddress = isInvalidEmailAddress,
|
||||
updateEmailAddress = viewModel::updateEmailAddress,
|
||||
name = name,
|
||||
nameError = nameError,
|
||||
isInvalidName = isInvalidName,
|
||||
updateName = viewModel::updateName,
|
||||
password = password,
|
||||
passwordError = passwordError,
|
||||
isInvalidPassword = isInvalidPassword,
|
||||
updatePassword = viewModel::updatePassword,
|
||||
confirmPassword = confirmPassword,
|
||||
confirmPasswordError = confirmPasswordError,
|
||||
isInvalidConfirmPassword = isInvalidConfirmPassword,
|
||||
updateConfirmPassword = viewModel::updateConfirmPassword,
|
||||
registerNewAccount = viewModel::registerNewAccount
|
||||
viewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -134,25 +134,25 @@ private fun RegistrationTopAppBar(onNavigateUp: () -> Unit, modifier: Modifier =
|
|||
|
||||
@Composable
|
||||
private fun RegistrationComponent(
|
||||
emailAddress: State<String>,
|
||||
emailAddressError: State<String>,
|
||||
isInvalidEmailAddress: State<Boolean>,
|
||||
updateEmailAddress: (String) -> Unit,
|
||||
name: State<String>,
|
||||
nameError: State<String>,
|
||||
isInvalidName: State<Boolean>,
|
||||
updateName: (String) -> Unit,
|
||||
password: State<String>,
|
||||
passwordError: State<String>,
|
||||
isInvalidPassword: State<Boolean>,
|
||||
updatePassword: (String) -> Unit,
|
||||
confirmPassword: State<String>,
|
||||
confirmPasswordError: State<String>,
|
||||
isInvalidConfirmPassword: State<Boolean>,
|
||||
updateConfirmPassword: (String) -> Unit,
|
||||
registerNewAccount: (String, String, String, String) -> Unit,
|
||||
viewModel: RegistrationScreenViewModel,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val emailAddress = viewModel.emailAddress.collectAsState()
|
||||
val emailAddressError = viewModel.emailAddressError.collectAsState()
|
||||
val isInvalidEmailAddress = viewModel.isInvalidEmailAddress.collectAsState(false)
|
||||
|
||||
val name = viewModel.name.collectAsState()
|
||||
val nameError = viewModel.nameError.collectAsState()
|
||||
val isInvalidName = viewModel.isInvalidName.collectAsState(false)
|
||||
|
||||
val password = viewModel.password.collectAsState()
|
||||
val passwordError = viewModel.passwordError.collectAsState()
|
||||
val isInvalidPassword = viewModel.isInvalidPassword.collectAsState(false)
|
||||
|
||||
val confirmPassword = viewModel.confirmPassword.collectAsState()
|
||||
val confirmPasswordError = viewModel.confirmPasswordError.collectAsState()
|
||||
val isInvalidConfirmPassword = viewModel.isInvalidConfirmPassword.collectAsState(false)
|
||||
|
||||
Column(modifier = modifier.verticalScroll(rememberScrollState())) {
|
||||
OutlinedTextField(
|
||||
value = emailAddress.value,
|
||||
|
@ -167,7 +167,7 @@ private fun RegistrationComponent(
|
|||
keyboardType = KeyboardType.Email,
|
||||
imeAction = ImeAction.Next
|
||||
),
|
||||
onValueChange = updateEmailAddress,
|
||||
onValueChange = { viewModel.onAction(RegistrationScreenAction.UpdateEmailAddress(it)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
|
@ -184,7 +184,7 @@ private fun RegistrationComponent(
|
|||
keyboardType = KeyboardType.Text,
|
||||
imeAction = ImeAction.Next
|
||||
),
|
||||
onValueChange = updateName,
|
||||
onValueChange = { viewModel.onAction(RegistrationScreenAction.UpdateName(it)) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(PaddingValues(top = 4.dp))
|
||||
|
@ -209,7 +209,7 @@ private fun RegistrationComponent(
|
|||
keyboardType = KeyboardType.Password,
|
||||
imeAction = ImeAction.Next
|
||||
),
|
||||
onValueChange = updatePassword,
|
||||
onValueChange = { viewModel.onAction(RegistrationScreenAction.UpdatePassword(it)) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(PaddingValues(top = 4.dp))
|
||||
|
@ -234,7 +234,7 @@ private fun RegistrationComponent(
|
|||
keyboardType = KeyboardType.Password,
|
||||
imeAction = ImeAction.Done
|
||||
),
|
||||
onValueChange = updateConfirmPassword,
|
||||
onValueChange = { viewModel.onAction(RegistrationScreenAction.UpdateConfirmPassword(it)) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(PaddingValues(top = 4.dp))
|
||||
|
@ -268,7 +268,7 @@ private fun RegistrationComponent(
|
|||
name,
|
||||
password,
|
||||
confirmPassword,
|
||||
registerNewAccount
|
||||
registerNewAccount = viewModel::registerNewAccount
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.register
|
||||
|
||||
sealed interface RegistrationScreenAction {
|
||||
data class UpdateEmailAddress(val emailAddress: String) : RegistrationScreenAction
|
||||
data class UpdateName(val name: String) : RegistrationScreenAction
|
||||
data class UpdatePassword(val password: String) : RegistrationScreenAction
|
||||
data class UpdateConfirmPassword(val confirmPassword: String) : RegistrationScreenAction
|
||||
data object ResetFields : RegistrationScreenAction
|
||||
data class RegisterNewUser(val emailAddress: String, val name: String, val password: String, val confirmPassword: String) : RegistrationScreenAction
|
||||
}
|
|
@ -6,9 +6,10 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
|||
import ing.bikeshedengineer.debtpirate.app.screen.auth.usecase.NewAccountRegistrationValidationResult
|
||||
import ing.bikeshedengineer.debtpirate.app.screen.auth.usecase.SubmitAccountRegistrationRequestUseCase
|
||||
import ing.bikeshedengineer.debtpirate.app.screen.auth.usecase.ValidateNewAccountRegistrationUseCase
|
||||
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.asStateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -18,7 +19,7 @@ import javax.inject.Inject
|
|||
class RegistrationScreenViewModel @Inject constructor(
|
||||
private val navigator: Navigator,
|
||||
private val validateNewAccount: ValidateNewAccountRegistrationUseCase,
|
||||
private val submitAccountRegistrationRequest: SubmitAccountRegistrationRequestUseCase
|
||||
private val submitAccountRegistrationRequest: SubmitAccountRegistrationRequestUseCase,
|
||||
) : ViewModel() {
|
||||
fun navigateUp() {
|
||||
viewModelScope.launch {
|
||||
|
@ -54,20 +55,40 @@ class RegistrationScreenViewModel @Inject constructor(
|
|||
val confirmPasswordError = _confirmPasswordError.asStateFlow()
|
||||
val isInvalidConfirmPassword = _confirmPasswordError.asStateFlow().map { it.isNotEmpty() }
|
||||
|
||||
fun updateEmailAddress(emailAddress: String) {
|
||||
_emailAddress.value = emailAddress
|
||||
}
|
||||
private val _onRegistrationComplete = MutableSharedFlow<Pair<String, String>>()
|
||||
val onRegistrationComplete = _onRegistrationComplete.asSharedFlow()
|
||||
|
||||
fun updateName(name: String) {
|
||||
_name.value = name
|
||||
}
|
||||
fun onAction(action: RegistrationScreenAction) {
|
||||
when (action) {
|
||||
is RegistrationScreenAction.UpdateEmailAddress -> {
|
||||
_emailAddress.value = action.emailAddress
|
||||
}
|
||||
|
||||
fun updatePassword(password: String) {
|
||||
_password.value = password
|
||||
}
|
||||
is RegistrationScreenAction.UpdateName -> {
|
||||
_name.value = action.name
|
||||
}
|
||||
|
||||
fun updateConfirmPassword(confirmPassword: String) {
|
||||
_confirmPassword.value = confirmPassword
|
||||
is RegistrationScreenAction.UpdatePassword -> {
|
||||
_password.value = action.password
|
||||
}
|
||||
|
||||
is RegistrationScreenAction.UpdateConfirmPassword -> {
|
||||
_confirmPassword.value = action.confirmPassword
|
||||
}
|
||||
|
||||
is RegistrationScreenAction.ResetFields -> {
|
||||
resetFields()
|
||||
}
|
||||
|
||||
is RegistrationScreenAction.RegisterNewUser -> {
|
||||
registerNewAccount(
|
||||
action.emailAddress,
|
||||
action.name,
|
||||
action.password,
|
||||
action.confirmPassword
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun registerNewAccount(
|
||||
|
@ -81,10 +102,12 @@ class RegistrationScreenViewModel @Inject constructor(
|
|||
if (fieldsAreValid) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
// TODO: Store the registration result data in the store
|
||||
val result =
|
||||
submitAccountRegistrationRequest(emailAddress, name, confirmPassword)
|
||||
|
||||
navigator.navigate(Destination.LoginScreen)
|
||||
_onRegistrationComplete.emit(Pair(emailAddress, confirmPassword))
|
||||
|
||||
resetFields()
|
||||
} catch (err: Throwable) {
|
||||
// TODO...
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package ing.bikeshedengineer.debtpirate.domain.model
|
||||
|
||||
sealed interface AccountManagerResult {
|
||||
data class Success(val username: String) : AccountManagerResult
|
||||
data object Unavailable : AccountManagerResult
|
||||
data object Canceled : AccountManagerResult
|
||||
data object Failure : AccountManagerResult
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
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.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(emailAddress)
|
||||
} catch (err: CreateCredentialNoCreateOptionException) {
|
||||
Log.w(
|
||||
"DebtPirate::AccountManager",
|
||||
"Cannot store credentials; a Google account isn't associated with this device"
|
||||
)
|
||||
AccountManagerResult.Unavailable
|
||||
} catch (err: CreateCredentialCancellationException) {
|
||||
err.printStackTrace()
|
||||
AccountManagerResult.Canceled
|
||||
} catch (err: CreateCredentialException) {
|
||||
err.printStackTrace()
|
||||
AccountManagerResult.Failure
|
||||
}
|
||||
}
|
||||
}
|
|
@ -44,8 +44,6 @@ private val darkScheme = darkColorScheme(
|
|||
|
||||
@Composable
|
||||
fun DebtPirateTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
dynamicColor: Boolean = true,
|
||||
content: @Composable() () -> Unit
|
||||
) {
|
||||
val colorScheme = darkScheme
|
||||
|
|
|
@ -25,12 +25,15 @@ okhttp = "4.10.0"
|
|||
retrofit = "2.9.0"
|
||||
hiltNavigationCompose = "1.2.0"
|
||||
fonts = "1.7.5"
|
||||
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-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" }
|
||||
androidx-credentials-compat = { group = "androidx.credentials", name = "credentials-play-services-auth", version.ref = "credentialManager" }
|
||||
androidx-datastore = { group = "androidx.datastore", name = "datastore", version.ref = "datastore" }
|
||||
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||
|
|
Loading…
Add table
Reference in a new issue