mirror of
https://github.com/openMF/mobile-wallet.git
synced 2026-02-06 15:56:53 +00:00
[MW-225] feat(auth): Implement full Sign In & Sign Up flow with validation (#1872)
This commit is contained in:
parent
3992e6f696
commit
df938aba4e
@ -1,5 +1,5 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="mifospay-ios" type="KmmRunConfiguration" factoryName="iOS Application" CONFIG_VERSION="1" XCODE_PROJECT="$PROJECT_DIR$/mifospay-ios/iosApp.xcodeproj">
|
||||
<configuration default="false" name="mifospay-ios" type="KmmRunConfiguration" factoryName="iOS Application" CONFIG_VERSION="1" XCODE_PROJECT="$PROJECT_DIR$/mifospay-ios/iosApp.xcodeproj" XCODE_CONFIGURATION="Debug" XCODE_SCHEME="iosApp">
|
||||
<method v="2">
|
||||
<option name="com.jetbrains.kmm.ios.BuildIOSAppTask" enabled="true" />
|
||||
</method>
|
||||
|
||||
@ -10,6 +10,8 @@
|
||||
plugins {
|
||||
alias(libs.plugins.mifospay.kmp.library)
|
||||
alias(libs.plugins.kotlin.parcelize)
|
||||
alias(libs.plugins.compose.compiler)
|
||||
alias(libs.plugins.jetbrainsCompose)
|
||||
}
|
||||
|
||||
android {
|
||||
@ -40,6 +42,8 @@ kotlin {
|
||||
api(libs.squareup.okio)
|
||||
api(libs.jb.kotlin.stdlib)
|
||||
api(libs.kotlinx.datetime)
|
||||
implementation(compose.components.resources)
|
||||
implementation(libs.jb.composeRuntime)
|
||||
}
|
||||
|
||||
androidMain.dependencies {
|
||||
|
||||
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
|
||||
*/
|
||||
package org.mifospay.core.common.dialogManager
|
||||
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
|
||||
/**
|
||||
* A singleton for managing dialog state across the app.
|
||||
* It works with [DialogMessage] to determine what kind of dialog should be shown.
|
||||
*
|
||||
* This is typically observed in the UI to display a loading spinner or error message.
|
||||
*/
|
||||
object DialogManager {
|
||||
|
||||
private val _dialogMessage = MutableStateFlow<DialogMessage>(DialogMessage.None)
|
||||
|
||||
/**
|
||||
* Public read-only dialog message flow.
|
||||
* UI layers should collect this to react to dialog state changes.
|
||||
*/
|
||||
val dialogMessage: StateFlow<DialogMessage> = _dialogMessage.asStateFlow()
|
||||
|
||||
/**
|
||||
* Dismisses any currently shown dialog.
|
||||
*
|
||||
* ### Example:
|
||||
* ```
|
||||
* DialogManager.dismissDialog()
|
||||
* ```
|
||||
*/
|
||||
fun dismissDialog() {
|
||||
_dialogMessage.value = DialogMessage.None
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a loading dialog.
|
||||
*
|
||||
* ### Example:
|
||||
* ```
|
||||
* DialogManager.showLoading()
|
||||
* ```
|
||||
*/
|
||||
fun showLoading() {
|
||||
_dialogMessage.value = DialogMessage.Loading
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a plain string message in a dialog.
|
||||
*
|
||||
* @param message The text to display.
|
||||
*
|
||||
* ### Example:
|
||||
* ```
|
||||
* DialogManager.showMessage("Invalid email address.")
|
||||
* ```
|
||||
*/
|
||||
fun showMessage(message: String) {
|
||||
_dialogMessage.value = DialogMessage.StringMessage(message)
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a localized string resource message in a dialog.
|
||||
*
|
||||
* @param message The resource ID to display.
|
||||
*
|
||||
* ### Example:
|
||||
* ```
|
||||
* DialogManager.showMessage(Res.string.feature_auth_error_email_required)
|
||||
* ```
|
||||
*/
|
||||
fun showMessage(message: StringResource) {
|
||||
_dialogMessage.value = DialogMessage.ResourceMessage(message)
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows setting any [DialogMessage] manually.
|
||||
*
|
||||
* @param message The [DialogMessage] to show.
|
||||
*
|
||||
* ### Example:
|
||||
* ```
|
||||
* DialogManager.showMessage(DialogMessage.Loading)
|
||||
* ```
|
||||
*/
|
||||
fun showMessage(message: DialogMessage) {
|
||||
_dialogMessage.value = message
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
|
||||
*/
|
||||
package org.mifospay.core.common.dialogManager
|
||||
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
|
||||
/**
|
||||
* Represents the possible types of dialog messages shown in the application.
|
||||
* Used in combination with [DialogManager] to control dialog visibility and content.
|
||||
*/
|
||||
sealed interface DialogMessage {
|
||||
|
||||
/**
|
||||
* Represents the absence of a dialog message.
|
||||
* Used to indicate that no dialog should be displayed.
|
||||
*/
|
||||
data object None : DialogMessage
|
||||
|
||||
/**
|
||||
* Represents a loading dialog.
|
||||
* Used to indicate an ongoing operation to the user.
|
||||
*/
|
||||
data object Loading : DialogMessage
|
||||
|
||||
/**
|
||||
* Represents a dialog that shows a plain string message.
|
||||
*
|
||||
* @property message The message to be displayed in the dialog.
|
||||
*/
|
||||
class StringMessage(val message: String) : DialogMessage
|
||||
|
||||
/**
|
||||
* Represents a dialog that shows a localized string resource message.
|
||||
*
|
||||
* @property message A [StringResource] ID for localization.
|
||||
*/
|
||||
class ResourceMessage(val message: StringResource) : DialogMessage
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Converts a [Throwable] into a [DialogMessage].
|
||||
* Uses [StringMessage] if the exception has a non-blank message,
|
||||
* otherwise falls back to a generic error.
|
||||
*
|
||||
* @return [StringMessage] based on content.
|
||||
*
|
||||
* ### Example:
|
||||
* ```
|
||||
* try {
|
||||
* doSomethingRisky()
|
||||
* } catch (e: Exception) {
|
||||
* DialogManager.showMessage(e.toDialogMessage())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
fun Throwable.toDialogMessage(): DialogMessage {
|
||||
return StringMessage(message ?: "Unknown error occurred.")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
|
||||
*/
|
||||
package org.mifospay.core.common.utils
|
||||
|
||||
/**
|
||||
* Formats a list of strings into a bullet-pointed multiline string.
|
||||
*
|
||||
* Each non-blank line is trimmed and prefixed with a Unicode bullet (•) followed by a space.
|
||||
* Empty or whitespace-only strings are ignored.
|
||||
*
|
||||
* @param lines A list of strings to be formatted.
|
||||
* @return A single string where each line is prefixed with a bullet point and separated by a newline.
|
||||
*
|
||||
* ### Example:
|
||||
* ```
|
||||
* val feedback = listOf(
|
||||
* "The password must be at least 12 characters long",
|
||||
* "Include at least one number"
|
||||
* )
|
||||
*
|
||||
* val result = formatAsBulletPoints(feedback)
|
||||
* println(result)
|
||||
* ```
|
||||
*
|
||||
* ### Output:
|
||||
* ```
|
||||
* • The password must be at least 12 characters long
|
||||
* • Include at least one number
|
||||
* ```
|
||||
*/
|
||||
fun formatAsBulletPoints(lines: List<String>): String {
|
||||
return lines
|
||||
.map { it.trim() }
|
||||
.filter { it.isNotEmpty() }
|
||||
.joinToString(separator = "\n") { "• $it" }
|
||||
}
|
||||
@ -11,6 +11,8 @@ plugins {
|
||||
alias(libs.plugins.mifospay.kmp.library)
|
||||
alias(libs.plugins.kotlin.parcelize)
|
||||
id("kotlinx-serialization")
|
||||
alias(libs.plugins.compose.compiler)
|
||||
alias(libs.plugins.jetbrainsCompose)
|
||||
}
|
||||
|
||||
android {
|
||||
@ -32,6 +34,8 @@ kotlin {
|
||||
implementation(projects.core.network)
|
||||
implementation(projects.core.analytics)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
implementation(libs.jb.composeRuntime)
|
||||
implementation(compose.components.resources)
|
||||
}
|
||||
|
||||
androidMain.dependencies {
|
||||
|
||||
22016
core/data/src/commonMain/composeResources/files/countries.json
Normal file
22016
core/data/src/commonMain/composeResources/files/countries.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -14,6 +14,7 @@ import org.koin.core.qualifier.named
|
||||
import org.koin.dsl.module
|
||||
import org.mifospay.core.common.MifosDispatchers
|
||||
import org.mifospay.core.data.repository.AccountRepository
|
||||
import org.mifospay.core.data.repository.AssetRepository
|
||||
import org.mifospay.core.data.repository.AuthenticationRepository
|
||||
import org.mifospay.core.data.repository.BeneficiaryRepository
|
||||
import org.mifospay.core.data.repository.ClientRepository
|
||||
@ -32,25 +33,26 @@ import org.mifospay.core.data.repository.StandingInstructionRepository
|
||||
import org.mifospay.core.data.repository.ThirdPartyTransferRepository
|
||||
import org.mifospay.core.data.repository.TwoFactorAuthRepository
|
||||
import org.mifospay.core.data.repository.UserRepository
|
||||
import org.mifospay.core.data.repositoryImp.AccountRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImp.AuthenticationRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImp.BeneficiaryRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImp.ClientRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImp.DocumentRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImp.InvoiceRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImp.KycLevelRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImp.LocalAssetRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImp.NotificationRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImp.RegistrationRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImp.RunReportRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImp.SavedCardRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImp.SavingsAccountRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImp.SearchRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImp.SelfServiceRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImp.StandingInstructionRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImp.ThirdPartyTransferRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImp.TwoFactorAuthRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImp.UserRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImpl.AccountRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImpl.AssetRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImpl.AuthenticationRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImpl.BeneficiaryRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImpl.ClientRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImpl.DocumentRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImpl.InvoiceRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImpl.KycLevelRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImpl.LocalAssetRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImpl.NotificationRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImpl.RegistrationRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImpl.RunReportRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImpl.SavedCardRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImpl.SavingsAccountRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImpl.SearchRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImpl.SelfServiceRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImpl.StandingInstructionRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImpl.ThirdPartyTransferRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImpl.TwoFactorAuthRepositoryImpl
|
||||
import org.mifospay.core.data.repositoryImpl.UserRepositoryImpl
|
||||
import org.mifospay.core.data.util.NetworkMonitor
|
||||
import org.mifospay.core.data.util.TimeZoneMonitor
|
||||
|
||||
@ -60,6 +62,7 @@ private val unconfined = named(MifosDispatchers.Unconfined.name)
|
||||
val RepositoryModule = module {
|
||||
single<Json> { Json { ignoreUnknownKeys = true } }
|
||||
|
||||
single<AssetRepository> { AssetRepositoryImpl() }
|
||||
single<AccountRepository> { AccountRepositoryImpl(get(), get(ioDispatcher)) }
|
||||
single<AuthenticationRepository> {
|
||||
AuthenticationRepositoryImpl(get(), get(ioDispatcher))
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
|
||||
*/
|
||||
package org.mifospay.core.data.repository
|
||||
|
||||
import org.mifospay.core.common.DataState
|
||||
|
||||
interface AssetRepository {
|
||||
suspend fun getCountriesWithStates(): DataState<Map<String, List<String>>>
|
||||
}
|
||||
@ -7,7 +7,7 @@
|
||||
*
|
||||
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
|
||||
*/
|
||||
package org.mifospay.core.data.repositoryImp
|
||||
package org.mifospay.core.data.repositoryImpl
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
|
||||
*/
|
||||
package org.mifospay.core.data.repositoryImpl
|
||||
|
||||
import kotlinx.serialization.json.Json
|
||||
import mobile_wallet.core.data.generated.resources.Res
|
||||
import org.jetbrains.compose.resources.ExperimentalResourceApi
|
||||
import org.mifospay.core.common.DataState
|
||||
import org.mifospay.core.data.repository.AssetRepository
|
||||
import org.mifospay.core.model.utils.Country
|
||||
|
||||
class AssetRepositoryImpl : AssetRepository {
|
||||
@OptIn(ExperimentalResourceApi::class)
|
||||
override suspend fun getCountriesWithStates(): DataState<Map<String, List<String>>> {
|
||||
return try {
|
||||
val json = Json { ignoreUnknownKeys = true }
|
||||
val bytes = Res.readBytes("files/countries.json")
|
||||
val jsonString = bytes.decodeToString()
|
||||
val countries = json.decodeFromString<List<Country>>(jsonString)
|
||||
DataState.Success(
|
||||
countries.associate { country ->
|
||||
country.name to country.states.map { it.name }
|
||||
},
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
DataState.Error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7,7 +7,7 @@
|
||||
*
|
||||
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
|
||||
*/
|
||||
package org.mifospay.core.data.repositoryImp
|
||||
package org.mifospay.core.data.repositoryImpl
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.withContext
|
||||
@ -7,7 +7,7 @@
|
||||
*
|
||||
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
|
||||
*/
|
||||
package org.mifospay.core.data.repositoryImp
|
||||
package org.mifospay.core.data.repositoryImpl
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@ -7,7 +7,7 @@
|
||||
*
|
||||
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
|
||||
*/
|
||||
package org.mifospay.core.data.repositoryImp
|
||||
package org.mifospay.core.data.repositoryImpl
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@ -7,7 +7,7 @@
|
||||
*
|
||||
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
|
||||
*/
|
||||
package org.mifospay.core.data.repositoryImp
|
||||
package org.mifospay.core.data.repositoryImpl
|
||||
|
||||
import io.ktor.client.request.forms.MultiPartFormDataContent
|
||||
import io.ktor.client.request.forms.formData
|
||||
@ -7,7 +7,7 @@
|
||||
*
|
||||
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
|
||||
*/
|
||||
package org.mifospay.core.data.repositoryImp
|
||||
package org.mifospay.core.data.repositoryImpl
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@ -7,7 +7,7 @@
|
||||
*
|
||||
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
|
||||
*/
|
||||
package org.mifospay.core.data.repositoryImp
|
||||
package org.mifospay.core.data.repositoryImpl
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@ -7,7 +7,7 @@
|
||||
*
|
||||
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
|
||||
*/
|
||||
package org.mifospay.core.data.repositoryImp
|
||||
package org.mifospay.core.data.repositoryImpl
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
@ -7,7 +7,7 @@
|
||||
*
|
||||
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
|
||||
*/
|
||||
package org.mifospay.core.data.repositoryImp
|
||||
package org.mifospay.core.data.repositoryImpl
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@ -7,7 +7,7 @@
|
||||
*
|
||||
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
|
||||
*/
|
||||
package org.mifospay.core.data.repositoryImp
|
||||
package org.mifospay.core.data.repositoryImpl
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.withContext
|
||||
@ -7,7 +7,7 @@
|
||||
*
|
||||
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
|
||||
*/
|
||||
package org.mifospay.core.data.repositoryImp
|
||||
package org.mifospay.core.data.repositoryImpl
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@ -7,7 +7,7 @@
|
||||
*
|
||||
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
|
||||
*/
|
||||
package org.mifospay.core.data.repositoryImp
|
||||
package org.mifospay.core.data.repositoryImpl
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@ -7,7 +7,7 @@
|
||||
*
|
||||
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
|
||||
*/
|
||||
package org.mifospay.core.data.repositoryImp
|
||||
package org.mifospay.core.data.repositoryImpl
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@ -7,7 +7,7 @@
|
||||
*
|
||||
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
|
||||
*/
|
||||
package org.mifospay.core.data.repositoryImp
|
||||
package org.mifospay.core.data.repositoryImpl
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.withContext
|
||||
@ -7,7 +7,7 @@
|
||||
*
|
||||
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
|
||||
*/
|
||||
package org.mifospay.core.data.repositoryImp
|
||||
package org.mifospay.core.data.repositoryImpl
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
@ -7,7 +7,7 @@
|
||||
*
|
||||
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
|
||||
*/
|
||||
package org.mifospay.core.data.repositoryImp
|
||||
package org.mifospay.core.data.repositoryImpl
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@ -7,7 +7,7 @@
|
||||
*
|
||||
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
|
||||
*/
|
||||
package org.mifospay.core.data.repositoryImp
|
||||
package org.mifospay.core.data.repositoryImpl
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@ -7,7 +7,7 @@
|
||||
*
|
||||
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
|
||||
*/
|
||||
package org.mifospay.core.data.repositoryImp
|
||||
package org.mifospay.core.data.repositoryImpl
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@ -7,7 +7,7 @@
|
||||
*
|
||||
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
|
||||
*/
|
||||
package org.mifospay.core.data.repositoryImp
|
||||
package org.mifospay.core.data.repositoryImpl
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@ -53,7 +53,7 @@ fun MifosOutlinedTextField(
|
||||
onValueChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
isError: Boolean = false,
|
||||
errorMessage: String = "",
|
||||
errorMessage: String? = null,
|
||||
singleLine: Boolean = false,
|
||||
showClearIcon: Boolean = true,
|
||||
readOnly: Boolean = false,
|
||||
@ -74,9 +74,10 @@ fun MifosOutlinedTextField(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
label = label,
|
||||
isError = isError,
|
||||
readOnly = readOnly,
|
||||
supportingText = {
|
||||
if (isError) {
|
||||
supportingText = errorMessage?.let {
|
||||
{
|
||||
Text(text = errorMessage)
|
||||
}
|
||||
},
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
|
||||
*/
|
||||
package org.mifospay.core.model.utils
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Country(
|
||||
val code2: String,
|
||||
val code3: String,
|
||||
val name: String,
|
||||
val capital: String,
|
||||
val region: String,
|
||||
val subregion: String,
|
||||
val states: List<State>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class State(
|
||||
val code: String,
|
||||
val name: String,
|
||||
)
|
||||
@ -12,4 +12,17 @@
|
||||
<string name="core_ui_retry">Retry</string>
|
||||
<string name="core_ui_error_occurred">Error Occurred!</string>
|
||||
<string name="core_ui_try_again">Please check your connection or try again</string>
|
||||
<string name="core_ui_password_requirements">Password Requirements</string>
|
||||
<string name="core_ui_error_icon_description">Error</string>
|
||||
|
||||
<!-- Password Strength Levels -->
|
||||
<string-array name="core_ui_password_strength_labels">
|
||||
<item /> <!-- NONE -->
|
||||
<item>Weak</item> <!-- WEAK_1 -->
|
||||
<item>Weak</item> <!-- WEAK_2 -->
|
||||
<item>Weak</item> <!-- WEAK_3 -->
|
||||
<item>Good</item> <!-- GOOD -->
|
||||
<item>Strong</item> <!-- STRONG -->
|
||||
<item>Very Strong</item> <!-- VERY_STRONG -->
|
||||
</string-array>
|
||||
</resources>
|
||||
@ -9,6 +9,7 @@
|
||||
*/
|
||||
package org.mifospay.core.ui
|
||||
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.Icon
|
||||
@ -49,13 +50,16 @@ fun MifosPasswordField(
|
||||
readOnly: Boolean = false,
|
||||
singleLine: Boolean = true,
|
||||
hint: String? = null,
|
||||
isError: Boolean = false,
|
||||
showPasswordTestTag: String? = null,
|
||||
autoFocus: Boolean = false,
|
||||
keyboardType: KeyboardType = KeyboardType.Password,
|
||||
imeAction: ImeAction = ImeAction.Default,
|
||||
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
MifosCustomTextField(
|
||||
modifier = modifier
|
||||
.tabNavigation()
|
||||
@ -74,6 +78,7 @@ fun MifosPasswordField(
|
||||
keyboardType = keyboardType,
|
||||
imeAction = imeAction,
|
||||
),
|
||||
isError = isError,
|
||||
keyboardActions = keyboardActions,
|
||||
supportingText = hint?.let {
|
||||
{
|
||||
@ -91,9 +96,9 @@ fun MifosPasswordField(
|
||||
),
|
||||
) {
|
||||
val imageVector = if (showPassword) {
|
||||
MifosIcons.OutlinedVisibilityOff
|
||||
} else {
|
||||
MifosIcons.OutlinedVisibility
|
||||
} else {
|
||||
MifosIcons.OutlinedVisibilityOff
|
||||
}
|
||||
|
||||
Icon(
|
||||
@ -106,6 +111,7 @@ fun MifosPasswordField(
|
||||
textStyle = TextStyle(
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
),
|
||||
interactionSource = interactionSource,
|
||||
)
|
||||
if (autoFocus) {
|
||||
LaunchedEffect(Unit) { focusRequester.requestFocus() }
|
||||
|
||||
@ -10,8 +10,10 @@
|
||||
package org.mifospay.core.ui
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@ -21,11 +23,16 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.CheckCircle
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
@ -33,14 +40,224 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.TransformOrigin
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import mobile_wallet.core.ui.generated.resources.Res
|
||||
import mobile_wallet.core.ui.generated.resources.core_ui_error_icon_description
|
||||
import mobile_wallet.core.ui.generated.resources.core_ui_password_requirements
|
||||
import mobile_wallet.core.ui.generated.resources.core_ui_password_strength_labels
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
import org.jetbrains.compose.resources.stringArrayResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||
import org.mifospay.core.designsystem.icon.MifosIcons
|
||||
import org.mifospay.core.designsystem.theme.MifosTheme
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
fun CombinedPasswordErrorCard(
|
||||
passwordStrengthState: PasswordStrengthState,
|
||||
currentCharacterCount: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
errorText: StringResource? = null,
|
||||
errors: List<String> = emptyList(),
|
||||
minimumCharacterCount: Int? = null,
|
||||
isPasswordFieldFocused: Boolean = false,
|
||||
) {
|
||||
val hasErrors = errorText != null || errors.isNotEmpty()
|
||||
|
||||
val widthPercent by animateFloatAsState(
|
||||
targetValue = when (passwordStrengthState) {
|
||||
PasswordStrengthState.NONE -> 0f
|
||||
PasswordStrengthState.WEAK_1 -> .25f
|
||||
PasswordStrengthState.WEAK_2 -> .5f
|
||||
PasswordStrengthState.WEAK_3 -> .66f
|
||||
PasswordStrengthState.GOOD -> .82f
|
||||
PasswordStrengthState.STRONG -> 1f
|
||||
PasswordStrengthState.VERY_STRONG -> 1f
|
||||
},
|
||||
label = "Width Percent State",
|
||||
)
|
||||
|
||||
val indicatorColor = when (passwordStrengthState) {
|
||||
PasswordStrengthState.NONE -> MaterialTheme.colorScheme.error
|
||||
PasswordStrengthState.WEAK_1 -> MaterialTheme.colorScheme.error
|
||||
PasswordStrengthState.WEAK_2 -> MaterialTheme.colorScheme.error
|
||||
PasswordStrengthState.WEAK_3 -> weakColor
|
||||
PasswordStrengthState.GOOD -> MaterialTheme.colorScheme.primary
|
||||
PasswordStrengthState.STRONG -> strongColor
|
||||
PasswordStrengthState.VERY_STRONG -> Color.Magenta
|
||||
}
|
||||
|
||||
val animatedIndicatorColor by animateColorAsState(
|
||||
targetValue = indicatorColor,
|
||||
label = "Indicator Color State",
|
||||
)
|
||||
|
||||
val strengthLabels = stringArrayResource(resource = Res.array.core_ui_password_strength_labels)
|
||||
val strengthLabel = strengthLabels[passwordStrengthState.ordinal]
|
||||
|
||||
AnimatedVisibility(visible = hasErrors || isPasswordFieldFocused) {
|
||||
Card(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.testTag("passwordErrorCard"),
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.error.copy(alpha = 0.05f),
|
||||
),
|
||||
border = BorderStroke(
|
||||
width = 1.dp,
|
||||
color = MaterialTheme.colorScheme.error.copy(alpha = 0.2f),
|
||||
),
|
||||
) {
|
||||
Column {
|
||||
// Top border strength indicator
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(4.dp)
|
||||
.background(MaterialTheme.colorScheme.surfaceContainerHigh),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(topStart = 8.dp, topEnd = 8.dp))
|
||||
.graphicsLayer {
|
||||
transformOrigin =
|
||||
TransformOrigin(pivotFractionX = 0f, pivotFractionY = 0f)
|
||||
scaleX = widthPercent
|
||||
}
|
||||
.drawBehind {
|
||||
drawRect(animatedIndicatorColor)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.padding(12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
// Header row with "Password Requirements" and strength label
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = MifosIcons.OutlinedInfo,
|
||||
contentDescription = stringResource(Res.string.core_ui_error_icon_description),
|
||||
tint = MaterialTheme.colorScheme.error,
|
||||
modifier = Modifier.size(16.dp),
|
||||
)
|
||||
Text(
|
||||
text = stringResource(Res.string.core_ui_password_requirements),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
fontWeight = FontWeight.Medium,
|
||||
)
|
||||
}
|
||||
|
||||
if (strengthLabel.isNotEmpty()) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = animatedIndicatorColor,
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
)
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
) {
|
||||
Text(
|
||||
text = strengthLabel,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onPrimary,
|
||||
fontWeight = FontWeight.Medium,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Minimum character count indicator if provided
|
||||
minimumCharacterCount?.let { minCount ->
|
||||
MinimumCharacterCount(
|
||||
minimumRequirementMet = currentCharacterCount >= minCount,
|
||||
minimumCharacterCount = minCount,
|
||||
)
|
||||
}
|
||||
|
||||
// Error text if provided
|
||||
errorText?.let {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag("passwordError"),
|
||||
verticalAlignment = Alignment.Top,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(4.dp)
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
shape = CircleShape,
|
||||
)
|
||||
.padding(top = 6.dp),
|
||||
)
|
||||
Text(
|
||||
text = stringResource(it),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Error list
|
||||
errors.forEachIndexed { index, error ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag("passwordError_$index"),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(
|
||||
space = 8.dp,
|
||||
alignment = Alignment.CenterHorizontally,
|
||||
),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(4.dp)
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
shape = CircleShape,
|
||||
)
|
||||
.padding(top = 6.dp),
|
||||
)
|
||||
Text(
|
||||
text = error,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod", "MagicNumber")
|
||||
@Composable
|
||||
fun PasswordStrengthIndicator(
|
||||
@ -74,15 +291,10 @@ fun PasswordStrengthIndicator(
|
||||
targetValue = indicatorColor,
|
||||
label = "Indicator Color State",
|
||||
)
|
||||
val label = when (state) {
|
||||
PasswordStrengthState.NONE -> ""
|
||||
PasswordStrengthState.WEAK_1 -> "Weak"
|
||||
PasswordStrengthState.WEAK_2 -> "Weak"
|
||||
PasswordStrengthState.WEAK_3 -> "Weak"
|
||||
PasswordStrengthState.GOOD -> "Good"
|
||||
PasswordStrengthState.STRONG -> "Strong"
|
||||
PasswordStrengthState.VERY_STRONG -> "Very Strong"
|
||||
}
|
||||
|
||||
val strengthLabels = stringArrayResource(resource = Res.array.core_ui_password_strength_labels)
|
||||
val strengthLabel = strengthLabels[state.ordinal]
|
||||
|
||||
Column(
|
||||
modifier = modifier,
|
||||
) {
|
||||
@ -118,7 +330,7 @@ fun PasswordStrengthIndicator(
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = label,
|
||||
text = strengthLabel,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = indicatorColor,
|
||||
)
|
||||
|
||||
@ -21,20 +21,7 @@ object PasswordChecker {
|
||||
private const val MAX_PASSWORD_LENGTH = 50
|
||||
|
||||
fun getPasswordStrengthResult(password: String): PasswordStrengthResult {
|
||||
val errors = buildList {
|
||||
if (password.isEmpty()) add("Password cannot be empty.")
|
||||
if (password.length > MAX_PASSWORD_LENGTH) add("Password is too long. Maximum length is $MAX_PASSWORD_LENGTH characters.")
|
||||
if (password.hasSpaces()) add("Password must not contain spaces.")
|
||||
if (password.hasConsecutiveRepetitions()) add("Password must not contain consecutive repetitive characters.")
|
||||
}
|
||||
|
||||
if (errors.isNotEmpty()) {
|
||||
return PasswordStrengthResult.Error(errors.joinToString("\n"))
|
||||
}
|
||||
|
||||
val result = getPasswordStrength(password)
|
||||
|
||||
return PasswordStrengthResult.Success(result)
|
||||
return PasswordStrengthResult.Success(getPasswordStrength(password))
|
||||
}
|
||||
|
||||
private fun getPasswordStrength(password: String): PasswordStrength {
|
||||
@ -51,9 +38,10 @@ object PasswordChecker {
|
||||
return when {
|
||||
length < MIN_PASSWORD_LENGTH -> PasswordStrength.LEVEL_0
|
||||
numTypesPresent == 1 -> PasswordStrength.LEVEL_1
|
||||
numTypesPresent == 2 || numTypesPresent == 3 -> PasswordStrength.LEVEL_2
|
||||
numTypesPresent == 2 -> PasswordStrength.LEVEL_2
|
||||
numTypesPresent == 4 && length >= STRONG_PASSWORD_LENGTH &&
|
||||
entropyBits >= MIN_ENTROPY_BITS -> PasswordStrength.LEVEL_5
|
||||
|
||||
numTypesPresent == 4 && length >= STRONG_PASSWORD_LENGTH -> PasswordStrength.LEVEL_4
|
||||
|
||||
else -> PasswordStrength.LEVEL_3
|
||||
@ -65,11 +53,16 @@ object PasswordChecker {
|
||||
return log2(charPool.toDouble().pow(password.length))
|
||||
}
|
||||
|
||||
// TODO: Move password feedback messages to string.xml — currently not possible as SignUpState uses Parcelable
|
||||
// and cannot hold List<StringResource>; revisit when SavedStateHandle usage is decoupled from state.
|
||||
fun getPasswordFeedback(password: String): List<String> {
|
||||
val feedback = mutableListOf<String>()
|
||||
|
||||
if (password.length < MIN_PASSWORD_LENGTH) {
|
||||
feedback.add("Password should be at least $MIN_PASSWORD_LENGTH characters long.")
|
||||
feedback.add("The password must be at least $MIN_PASSWORD_LENGTH characters long.")
|
||||
}
|
||||
if (password.length > MAX_PASSWORD_LENGTH) {
|
||||
feedback.add("The password must not exceed $MAX_PASSWORD_LENGTH characters.")
|
||||
}
|
||||
if (!password.any { it.isUpperCase() }) {
|
||||
feedback.add("Include at least one uppercase letter.")
|
||||
@ -83,14 +76,11 @@ object PasswordChecker {
|
||||
if (!password.any { !it.isLetterOrDigit() }) {
|
||||
feedback.add("Include at least one special character.")
|
||||
}
|
||||
if (password.length < STRONG_PASSWORD_LENGTH) {
|
||||
feedback.add("For a stronger password, use at least $STRONG_PASSWORD_LENGTH characters.")
|
||||
}
|
||||
if (password.hasConsecutiveRepetitions()) {
|
||||
feedback.add("Remove consecutive repeating characters.")
|
||||
feedback.add("Avoid using consecutive repeated characters.")
|
||||
}
|
||||
if (password.hasSpaces()) {
|
||||
feedback.add("Remove spaces.")
|
||||
feedback.add("Do not include spaces in the password.")
|
||||
}
|
||||
|
||||
return feedback
|
||||
|
||||
@ -31,6 +31,7 @@ kotlin {
|
||||
implementation(compose.components.uiToolingPreview)
|
||||
implementation(libs.jb.kotlin.stdlib)
|
||||
implementation(libs.kotlin.reflect)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
}
|
||||
|
||||
androidMain.dependencies {
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
|
||||
-->
|
||||
<resources>
|
||||
<!-- General Auth -->
|
||||
<string name="feature_auth_login">Login</string>
|
||||
<string name="feature_auth_welcome_back">Welcome back!</string>
|
||||
<string name="feature_auth_sign_up">Sign up.</string>
|
||||
@ -24,6 +25,8 @@
|
||||
<string name="feature_auth_validate_email">Please enter a valid email address</string>
|
||||
<string name="feature_auth_all_fields_are_mandatory">All fields are mandatory.</string>
|
||||
<string name="feature_auth_complete_your_registration">Complete your registration</string>
|
||||
|
||||
<!-- Labels -->
|
||||
<string name="feature_auth_first_name">First Name</string>
|
||||
<string name="feature_auth_last_name">Last Name</string>
|
||||
<string name="feature_auth_email">E-mail</string>
|
||||
@ -35,14 +38,14 @@
|
||||
<string name="feature_auth_state">State</string>
|
||||
<string name="feature_auth_country">Country</string>
|
||||
<string name="feature_auth_mobile_no">Mobile No</string>
|
||||
<string name="feature_auth_phone_number">Phone Number</string>
|
||||
<string name="feature_auth_complete">Complete</string>
|
||||
<string name="feature_auth_please_wait">Please wait…</string>
|
||||
<string name="feature_auth_mandatory">Mandatory field</string>
|
||||
<string name="feature_auth_password_cannot_be_empty">Password cannot be empty</string>
|
||||
<string name="feature_auth_password_must_be_least_6_characters">Password must be at least 6 characters</string>
|
||||
<string name="feature_auth_confirm_password_cannot_empty">Confirm password cannot be empty</string>
|
||||
<string name="feature_auth_passwords_do_not_match">Passwords do not match</string>
|
||||
|
||||
<!-- Password-->
|
||||
<string name="feature_auth_password_strength">Password Strength:</string>
|
||||
<string name="feature_auth_password_requirements">Please ensure password contains :\n- At least one uppercase character\n- At least one lowercase character\n- At least one numeric digit\n- At least one special character\n- With no space or consecutive repeating</string>
|
||||
|
||||
<!-- OTP Flow -->
|
||||
<string name="feature_auth_enter_mobile_number">Enter Mobile Number</string>
|
||||
<string name="feature_auth_enter_otp">Enter OTP</string>
|
||||
<string name="feature_auth_enter_mobile_number_description">It should be currently activated on your device
|
||||
@ -51,5 +54,23 @@
|
||||
<string name="feature_auth_enter_otp_received">Enter otp received on your registered device</string>
|
||||
<string name="feature_auth_verify_phone">Verify Phone</string>
|
||||
<string name="feature_auth_verify_otp">Verify Otp</string>
|
||||
<string name="feature_auth_phone_number">Phone Number</string>
|
||||
|
||||
<!-- Sign-up Validation Errors -->
|
||||
<string name="feature_auth_error_select_savings_account">Please select a savings account.</string>
|
||||
<string name="feature_auth_error_first_name_required">Please enter your first name.</string>
|
||||
<string name="feature_auth_error_last_name_required">Please enter your last name.</string>
|
||||
<string name="feature_auth_error_username_required">Please enter your username.</string>
|
||||
<string name="feature_auth_error_email_required">Please enter your email.</string>
|
||||
<string name="feature_auth_error_email_invalid">Please enter a valid email.</string>
|
||||
<string name="feature_auth_error_mobile_required">Please enter your mobile number.</string>
|
||||
<string name="feature_auth_error_mobile_invalid">Mobile number must be 10 digits long.</string>
|
||||
<string name="feature_auth_error_password_required">The password field cannot be empty.</string>
|
||||
<string name="feature_auth_error_password_invalid">Your password does not meet requirements.</string>
|
||||
<string name="feature_auth_error_confirm_password_required">The confirm password field cannot be empty.</string>
|
||||
<string name="feature_auth_error_passwords_mismatch">Passwords do not match.</string>
|
||||
<string name="feature_auth_error_address_line1_required">Please enter your address line 1.</string>
|
||||
<string name="feature_auth_error_address_line2_required">Please enter your address line 2.</string>
|
||||
<string name="feature_auth_error_pincode_required">Please enter your pin code.</string>
|
||||
<string name="feature_auth_error_country_required">Please enter your country.</string>
|
||||
<string name="feature_auth_error_state_required">Please enter your state.</string>
|
||||
</resources>
|
||||
@ -77,10 +77,8 @@ class LoginViewModel(
|
||||
private fun handleLoginResult(action: LoginAction.Internal.ReceiveLoginResult) {
|
||||
when (action.loginResult) {
|
||||
is DataState.Error -> {
|
||||
val message = action.loginResult.exception.message ?: ""
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = LoginState.DialogState.Error(message))
|
||||
it.copy(dialogState = LoginState.DialogState.Error(action.loginResult.message))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -9,6 +9,8 @@
|
||||
*/
|
||||
package org.mifospay.feature.auth.signup
|
||||
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
@ -58,6 +60,8 @@ import mobile_wallet.feature.auth.generated.resources.feature_auth_state
|
||||
import mobile_wallet.feature.auth.generated.resources.feature_auth_username
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import org.mifospay.core.common.dialogManager.DialogManager
|
||||
import org.mifospay.core.common.dialogManager.DialogMessage
|
||||
import org.mifospay.core.designsystem.component.BasicDialogState
|
||||
import org.mifospay.core.designsystem.component.LoadingDialogState
|
||||
import org.mifospay.core.designsystem.component.MifosBasicDialog
|
||||
@ -67,8 +71,10 @@ import org.mifospay.core.designsystem.component.MifosOutlinedTextField
|
||||
import org.mifospay.core.designsystem.component.MifosScaffold
|
||||
import org.mifospay.core.designsystem.component.MifosTopAppBar
|
||||
import org.mifospay.core.designsystem.icon.MifosIcons
|
||||
import org.mifospay.core.ui.CombinedPasswordErrorCard
|
||||
import org.mifospay.core.ui.DropdownBoxItem
|
||||
import org.mifospay.core.ui.ExposedDropdownBox
|
||||
import org.mifospay.core.ui.MifosPasswordField
|
||||
import org.mifospay.core.ui.PasswordStrengthIndicator
|
||||
import org.mifospay.core.ui.utils.EventsEffect
|
||||
|
||||
@Composable
|
||||
@ -82,6 +88,7 @@ internal fun SignupScreen(
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
val dialogMessage by DialogManager.dialogMessage.collectAsStateWithLifecycle()
|
||||
|
||||
EventsEffect(viewModel) { event ->
|
||||
when (event) {
|
||||
@ -96,7 +103,7 @@ internal fun SignupScreen(
|
||||
}
|
||||
|
||||
SignUpDialogs(
|
||||
dialogState = state.dialogState,
|
||||
dialogMessage = dialogMessage,
|
||||
onDismissRequest = remember(viewModel) {
|
||||
{ viewModel.trySendAction(SignUpAction.ErrorDialogDismiss) }
|
||||
},
|
||||
@ -164,7 +171,6 @@ private fun SignupScreenContent(
|
||||
value = state.firstNameInput,
|
||||
label = stringResource(Res.string.feature_auth_first_name),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isError = state.firstNameInput.isEmpty(),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
capitalization = KeyboardCapitalization.Words,
|
||||
),
|
||||
@ -179,7 +185,6 @@ private fun SignupScreenContent(
|
||||
value = state.lastNameInput,
|
||||
label = stringResource(Res.string.feature_auth_last_name),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isError = state.lastNameInput.isEmpty(),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
capitalization = KeyboardCapitalization.Words,
|
||||
),
|
||||
@ -194,7 +199,6 @@ private fun SignupScreenContent(
|
||||
value = state.userNameInput,
|
||||
label = stringResource(Res.string.feature_auth_username),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isError = state.userNameInput.isEmpty(),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
capitalization = KeyboardCapitalization.None,
|
||||
),
|
||||
@ -209,7 +213,6 @@ private fun SignupScreenContent(
|
||||
value = state.emailInput,
|
||||
label = stringResource(Res.string.feature_auth_email),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isError = state.emailInput.isEmpty(),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Email,
|
||||
),
|
||||
@ -224,7 +227,6 @@ private fun SignupScreenContent(
|
||||
value = state.mobileNumberInput,
|
||||
label = stringResource(Res.string.feature_auth_mobile_no),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isError = state.mobileNumberInput.isEmpty(),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Phone,
|
||||
),
|
||||
@ -237,6 +239,8 @@ private fun SignupScreenContent(
|
||||
item {
|
||||
Column {
|
||||
var showPassword by rememberSaveable { mutableStateOf(false) }
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val isFocused by interactionSource.collectIsFocusedAsState()
|
||||
|
||||
MifosPasswordField(
|
||||
value = state.passwordInput,
|
||||
@ -247,12 +251,15 @@ private fun SignupScreenContent(
|
||||
},
|
||||
showPassword = showPassword,
|
||||
showPasswordChange = { showPassword = !showPassword },
|
||||
interactionSource = interactionSource,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
PasswordStrengthIndicator(
|
||||
CombinedPasswordErrorCard(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
state = state.passwordStrengthState,
|
||||
errors = state.passwordFeedback,
|
||||
passwordStrengthState = state.passwordStrengthState,
|
||||
currentCharacterCount = state.passwordInput.length,
|
||||
isPasswordFieldFocused = isFocused,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -277,7 +284,6 @@ private fun SignupScreenContent(
|
||||
value = state.addressLine1Input,
|
||||
label = stringResource(Res.string.feature_auth_address_line_1),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isError = state.addressLine1Input.isEmpty(),
|
||||
onValueChange = {
|
||||
onAction(SignUpAction.AddressLine1InputChange(it))
|
||||
},
|
||||
@ -289,7 +295,6 @@ private fun SignupScreenContent(
|
||||
value = state.addressLine2Input,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
label = stringResource(Res.string.feature_auth_address_line_2),
|
||||
isError = state.addressLine2Input.isEmpty(),
|
||||
onValueChange = {
|
||||
onAction(SignUpAction.AddressLine2InputChange(it))
|
||||
},
|
||||
@ -301,7 +306,6 @@ private fun SignupScreenContent(
|
||||
value = state.pinCodeInput,
|
||||
label = stringResource(Res.string.feature_auth_pin_code),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isError = state.pinCodeInput.isEmpty(),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Number,
|
||||
),
|
||||
@ -317,25 +321,57 @@ private fun SignupScreenContent(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
MifosOutlinedTextField(
|
||||
value = state.countryInput,
|
||||
label = stringResource(Res.string.feature_auth_country),
|
||||
onValueChange = {
|
||||
onAction(SignUpAction.CountryInputChange(it))
|
||||
},
|
||||
modifier = Modifier.weight(1.5f),
|
||||
isError = state.countryInput.isEmpty(),
|
||||
)
|
||||
var isCountryDropdownExpanded by remember { mutableStateOf(false) }
|
||||
var isStateDropdownExpanded by remember { mutableStateOf(false) }
|
||||
|
||||
MifosOutlinedTextField(
|
||||
value = state.stateInput,
|
||||
label = stringResource(Res.string.feature_auth_state),
|
||||
onValueChange = {
|
||||
onAction(SignUpAction.StateInputChange(it))
|
||||
},
|
||||
// Country Dropdown
|
||||
ExposedDropdownBox(
|
||||
expanded = isCountryDropdownExpanded,
|
||||
label = stringResource(Res.string.feature_auth_country),
|
||||
value = state.countryInput,
|
||||
onExpandChange = { isCountryDropdownExpanded = it },
|
||||
modifier = Modifier.weight(1.5f),
|
||||
isError = state.stateInput.isEmpty(),
|
||||
)
|
||||
) {
|
||||
state.countriesWithStates.keys.forEach { country ->
|
||||
DropdownBoxItem(
|
||||
text = country,
|
||||
onClick = {
|
||||
onAction(SignUpAction.CountryInputChange(country))
|
||||
isCountryDropdownExpanded = false
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// State Dropdown
|
||||
if (state.statesForSelectedCountry.isNullOrEmpty()) {
|
||||
MifosOutlinedTextField(
|
||||
value = state.stateInput,
|
||||
label = stringResource(Res.string.feature_auth_state),
|
||||
onValueChange = {
|
||||
onAction(SignUpAction.StateInputChange(it))
|
||||
},
|
||||
modifier = Modifier.weight(1.5f),
|
||||
)
|
||||
} else {
|
||||
ExposedDropdownBox(
|
||||
expanded = isStateDropdownExpanded,
|
||||
label = stringResource(Res.string.feature_auth_state),
|
||||
value = state.stateInput,
|
||||
onExpandChange = { isStateDropdownExpanded = it },
|
||||
modifier = Modifier.weight(1.5f),
|
||||
) {
|
||||
state.statesForSelectedCountry.forEach { stateName ->
|
||||
DropdownBoxItem(
|
||||
text = stateName,
|
||||
onClick = {
|
||||
onAction(SignUpAction.StateInputChange(stateName))
|
||||
isStateDropdownExpanded = false
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -359,21 +395,24 @@ private fun SignupScreenContent(
|
||||
|
||||
@Composable
|
||||
private fun SignUpDialogs(
|
||||
dialogState: SignUpDialog?,
|
||||
dialogMessage: DialogMessage,
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
when (dialogState) {
|
||||
is SignUpDialog.Error -> MifosBasicDialog(
|
||||
visibilityState = BasicDialogState.Shown(
|
||||
message = dialogState.message,
|
||||
),
|
||||
onDismissRequest = onDismissRequest,
|
||||
)
|
||||
|
||||
is SignUpDialog.Loading -> MifosLoadingDialog(
|
||||
when (dialogMessage) {
|
||||
is DialogMessage.Loading -> MifosLoadingDialog(
|
||||
visibilityState = LoadingDialogState.Shown,
|
||||
)
|
||||
|
||||
null -> Unit
|
||||
is DialogMessage.StringMessage -> MifosBasicDialog(
|
||||
visibilityState = BasicDialogState.Shown(dialogMessage.message),
|
||||
onDismissRequest = onDismissRequest,
|
||||
)
|
||||
|
||||
is DialogMessage.ResourceMessage -> MifosBasicDialog(
|
||||
visibilityState = BasicDialogState.Shown(stringResource(dialogMessage.message)),
|
||||
onDismissRequest = onDismissRequest,
|
||||
)
|
||||
|
||||
is DialogMessage.None -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,16 +11,41 @@ package org.mifospay.feature.auth.signup
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import mobile_wallet.feature.auth.generated.resources.Res
|
||||
import mobile_wallet.feature.auth.generated.resources.feature_auth_error_address_line1_required
|
||||
import mobile_wallet.feature.auth.generated.resources.feature_auth_error_address_line2_required
|
||||
import mobile_wallet.feature.auth.generated.resources.feature_auth_error_confirm_password_required
|
||||
import mobile_wallet.feature.auth.generated.resources.feature_auth_error_country_required
|
||||
import mobile_wallet.feature.auth.generated.resources.feature_auth_error_email_invalid
|
||||
import mobile_wallet.feature.auth.generated.resources.feature_auth_error_email_required
|
||||
import mobile_wallet.feature.auth.generated.resources.feature_auth_error_first_name_required
|
||||
import mobile_wallet.feature.auth.generated.resources.feature_auth_error_last_name_required
|
||||
import mobile_wallet.feature.auth.generated.resources.feature_auth_error_mobile_invalid
|
||||
import mobile_wallet.feature.auth.generated.resources.feature_auth_error_mobile_required
|
||||
import mobile_wallet.feature.auth.generated.resources.feature_auth_error_password_required
|
||||
import mobile_wallet.feature.auth.generated.resources.feature_auth_error_passwords_mismatch
|
||||
import mobile_wallet.feature.auth.generated.resources.feature_auth_error_pincode_required
|
||||
import mobile_wallet.feature.auth.generated.resources.feature_auth_error_select_savings_account
|
||||
import mobile_wallet.feature.auth.generated.resources.feature_auth_error_state_required
|
||||
import mobile_wallet.feature.auth.generated.resources.feature_auth_error_username_required
|
||||
import org.mifospay.core.common.DataState
|
||||
import org.mifospay.core.common.IgnoredOnParcel
|
||||
import org.mifospay.core.common.Parcelable
|
||||
import org.mifospay.core.common.Parcelize
|
||||
import org.mifospay.core.common.dialogManager.DialogManager
|
||||
import org.mifospay.core.common.dialogManager.DialogMessage.Companion.toDialogMessage
|
||||
import org.mifospay.core.common.utils.formatAsBulletPoints
|
||||
import org.mifospay.core.common.utils.isValidEmail
|
||||
import org.mifospay.core.data.repository.AssetRepository
|
||||
import org.mifospay.core.data.repository.ClientRepository
|
||||
import org.mifospay.core.data.repository.SearchRepository
|
||||
import org.mifospay.core.data.repository.UserRepository
|
||||
@ -36,13 +61,12 @@ import org.mifospay.core.ui.utils.PasswordStrengthResult
|
||||
import org.mifospay.feature.auth.signup.SignUpAction.Internal.ReceivePasswordStrengthResult
|
||||
|
||||
private const val KEY_STATE = "signup_state"
|
||||
private const val MIN_PASSWORD_LENGTH = 12
|
||||
private const val MAX_PASSWORD_LENGTH = 50
|
||||
|
||||
class SignupViewModel(
|
||||
private val userRepository: UserRepository,
|
||||
private val searchRepository: SearchRepository,
|
||||
private val clientRepository: ClientRepository,
|
||||
private val assetRepository: AssetRepository,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : BaseViewModel<SignUpState, SignUpEvent, SignUpAction>(
|
||||
initialState = savedStateHandle[KEY_STATE] ?: SignUpState(),
|
||||
@ -71,6 +95,8 @@ class SignupViewModel(
|
||||
trySendAction(SignUpAction.BusinessNameInputChange(it))
|
||||
}
|
||||
}
|
||||
|
||||
loadCountriesFromJson()
|
||||
}
|
||||
|
||||
override fun handleAction(action: SignUpAction) {
|
||||
@ -151,7 +177,11 @@ class SignupViewModel(
|
||||
|
||||
is SignUpAction.CountryInputChange -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(countryInput = action.country)
|
||||
it.copy(
|
||||
countryInput = action.country,
|
||||
// reset state when country changes
|
||||
stateInput = "",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -160,9 +190,7 @@ class SignupViewModel(
|
||||
}
|
||||
|
||||
is SignUpAction.ErrorDialogDismiss -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = null)
|
||||
}
|
||||
DialogManager.dismissDialog()
|
||||
}
|
||||
|
||||
is ReceivePasswordStrengthResult -> handlePasswordStrengthResult(action)
|
||||
@ -170,12 +198,20 @@ class SignupViewModel(
|
||||
is SignUpAction.Internal.ReceiveRegisterResult -> handleSignUpResult(action)
|
||||
|
||||
is SignUpAction.SubmitClick -> handleSubmitClick()
|
||||
|
||||
is SignUpAction.LoadCountries -> loadCountriesFromJson()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePasswordInput(action: SignUpAction.PasswordInputChange) {
|
||||
// Update input:
|
||||
mutableStateFlow.update { it.copy(passwordInput = action.password, passwordError = null) }
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
passwordInput = action.password,
|
||||
passwordFeedback = PasswordChecker.getPasswordFeedback(action.password)
|
||||
.toPersistentList(),
|
||||
)
|
||||
}
|
||||
// Update password strength:
|
||||
passwordStrengthJob.cancel()
|
||||
if (action.password.isEmpty()) {
|
||||
@ -206,157 +242,95 @@ class SignupViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
is PasswordStrengthResult.Error -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(passwordError = result.message.toString(), passwordStrengthState = PasswordStrengthState.NONE)
|
||||
}
|
||||
}
|
||||
is PasswordStrengthResult.Error -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSignUpResult(action: SignUpAction.Internal.ReceiveRegisterResult) {
|
||||
when (val result = action.registerResult) {
|
||||
is DataState.Success -> {
|
||||
mutableStateFlow.update { it.copy(dialogState = null) }
|
||||
DialogManager.dismissDialog()
|
||||
sendEvent(SignUpEvent.NavigateToLogin(result.data))
|
||||
}
|
||||
|
||||
is DataState.Error -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = SignUpDialog.Error(result.exception.message.toString()))
|
||||
}
|
||||
DialogManager.showMessage(result.exception.toDialogMessage())
|
||||
}
|
||||
|
||||
DataState.Loading -> {
|
||||
mutableStateFlow.update { it.copy(dialogState = SignUpDialog.Loading) }
|
||||
DialogManager.showLoading()
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO:: move error messages to strings.xml
|
||||
private fun handleSubmitClick() = when {
|
||||
state.savingsProductId == 0 -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = SignUpDialog.Error("Please select a savings account."))
|
||||
}
|
||||
DialogManager.showMessage(Res.string.feature_auth_error_select_savings_account)
|
||||
}
|
||||
|
||||
state.firstNameInput.isEmpty() -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = SignUpDialog.Error("Please enter your first name."))
|
||||
}
|
||||
DialogManager.showMessage(Res.string.feature_auth_error_first_name_required)
|
||||
}
|
||||
|
||||
state.lastNameInput.isEmpty() -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = SignUpDialog.Error("Please enter your last name."))
|
||||
}
|
||||
DialogManager.showMessage(Res.string.feature_auth_error_last_name_required)
|
||||
}
|
||||
|
||||
state.userNameInput.isEmpty() -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = SignUpDialog.Error("Please enter your username."))
|
||||
}
|
||||
DialogManager.showMessage(Res.string.feature_auth_error_username_required)
|
||||
}
|
||||
|
||||
state.emailInput.isEmpty() -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = SignUpDialog.Error("Please enter your email."))
|
||||
}
|
||||
DialogManager.showMessage(Res.string.feature_auth_error_email_required)
|
||||
}
|
||||
|
||||
!state.emailInput.isValidEmail() -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = SignUpDialog.Error("Please enter a valid email."))
|
||||
}
|
||||
DialogManager.showMessage(Res.string.feature_auth_error_email_invalid)
|
||||
}
|
||||
|
||||
state.mobileNumberInput.isEmpty() -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = SignUpDialog.Error("Please enter your mobile number."))
|
||||
}
|
||||
DialogManager.showMessage(Res.string.feature_auth_error_mobile_required)
|
||||
}
|
||||
|
||||
state.mobileNumberInput.length < 10 -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = SignUpDialog.Error("Mobile number must be 10 digits long."))
|
||||
}
|
||||
DialogManager.showMessage(Res.string.feature_auth_error_mobile_invalid)
|
||||
}
|
||||
|
||||
state.passwordInput.length < MIN_PASSWORD_LENGTH -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialogState = SignUpDialog.Error(
|
||||
"Password must be at least $MIN_PASSWORD_LENGTH characters long.",
|
||||
),
|
||||
)
|
||||
}
|
||||
state.passwordInput.isEmpty() -> {
|
||||
DialogManager.showMessage(Res.string.feature_auth_error_password_required)
|
||||
}
|
||||
|
||||
state.passwordInput.length > MAX_PASSWORD_LENGTH -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialogState = SignUpDialog.Error(
|
||||
"Password must be less than $MAX_PASSWORD_LENGTH characters long.",
|
||||
),
|
||||
)
|
||||
}
|
||||
state.passwordFeedback.isNotEmpty() -> {
|
||||
val bulletListPasswordFeedback = formatAsBulletPoints(state.passwordFeedback)
|
||||
DialogManager.showMessage(bulletListPasswordFeedback)
|
||||
}
|
||||
|
||||
!state.isPasswordMatch -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = SignUpDialog.Error("Passwords do not match."))
|
||||
}
|
||||
state.confirmPasswordInput.isEmpty() -> {
|
||||
DialogManager.showMessage(Res.string.feature_auth_error_confirm_password_required)
|
||||
}
|
||||
|
||||
!state.isPasswordStrong -> {
|
||||
val errorMessage = state.passwordError?.takeIf { it.isNotBlank() }
|
||||
?: "Please ensure password contains :" +
|
||||
"\n- At least one uppercase character" +
|
||||
"\n- At least one lowercase character" +
|
||||
"\n- At least one numeric digit" +
|
||||
"\n- At least one special character"
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = SignUpDialog.Error(errorMessage.lines().joinToString("\n") { "- $it" }))
|
||||
}
|
||||
state.passwordInput != state.confirmPasswordInput -> {
|
||||
DialogManager.showMessage(Res.string.feature_auth_error_passwords_mismatch)
|
||||
}
|
||||
|
||||
state.addressLine1Input.isEmpty() -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = SignUpDialog.Error("Please enter your address line 1."))
|
||||
}
|
||||
DialogManager.showMessage(Res.string.feature_auth_error_address_line1_required)
|
||||
}
|
||||
|
||||
state.addressLine2Input.isEmpty() -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = SignUpDialog.Error("Please enter your address line 2."))
|
||||
}
|
||||
DialogManager.showMessage(Res.string.feature_auth_error_address_line2_required)
|
||||
}
|
||||
|
||||
state.pinCodeInput.isEmpty() -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = SignUpDialog.Error("Please enter your pin code."))
|
||||
}
|
||||
}
|
||||
|
||||
state.pinCodeInput.length < 6 -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = SignUpDialog.Error("Pin code must be 6 digits long."))
|
||||
}
|
||||
DialogManager.showMessage(Res.string.feature_auth_error_pincode_required)
|
||||
}
|
||||
|
||||
state.countryInput.isEmpty() -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = SignUpDialog.Error("Please enter your country."))
|
||||
}
|
||||
DialogManager.showMessage(Res.string.feature_auth_error_country_required)
|
||||
}
|
||||
|
||||
state.stateInput.isEmpty() -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = SignUpDialog.Error("Please enter your state."))
|
||||
}
|
||||
DialogManager.showMessage(Res.string.feature_auth_error_state_required)
|
||||
}
|
||||
|
||||
else -> initiateSignUp()
|
||||
@ -366,9 +340,7 @@ class SignupViewModel(
|
||||
Enhancement: Move the following code in to a Use Case
|
||||
*/
|
||||
private fun initiateSignUp() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = SignUpDialog.Loading)
|
||||
}
|
||||
DialogManager.showLoading()
|
||||
|
||||
val fieldsToCheck = mapOf(
|
||||
"Username" to state.userNameInput,
|
||||
@ -388,20 +360,18 @@ class SignupViewModel(
|
||||
|
||||
val errorMessages = results.mapNotNull { (label, result) ->
|
||||
when (result) {
|
||||
is DataState.Loading -> {}
|
||||
is DataState.Success -> {
|
||||
if (result.data.isNotEmpty()) "$label already exists." else null
|
||||
}
|
||||
|
||||
is DataState.Error ->
|
||||
result.exception.message
|
||||
?: "Error checking $label."
|
||||
else -> null
|
||||
"Unable to check if $label is unique. Please try again later."
|
||||
}
|
||||
}
|
||||
|
||||
if (errorMessages.isNotEmpty()) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = SignUpDialog.Error(errorMessages.joinToString("\n")))
|
||||
}
|
||||
DialogManager.showMessage(errorMessages.joinToString("\n"))
|
||||
} else {
|
||||
val newUser = NewUser(
|
||||
state.userNameInput,
|
||||
@ -419,10 +389,7 @@ class SignupViewModel(
|
||||
viewModelScope.launch {
|
||||
when (val result = userRepository.createUser(newUser)) {
|
||||
is DataState.Error -> {
|
||||
val message = result.exception.message.toString()
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = SignUpDialog.Error(message))
|
||||
}
|
||||
DialogManager.showMessage(result.exception.toDialogMessage())
|
||||
}
|
||||
|
||||
is DataState.Success -> {
|
||||
@ -455,9 +422,7 @@ class SignupViewModel(
|
||||
is DataState.Error -> {
|
||||
deleteUser(userId)
|
||||
val message = result.exception.message.toString()
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = SignUpDialog.Error(message))
|
||||
}
|
||||
DialogManager.showMessage(message)
|
||||
}
|
||||
|
||||
is DataState.Success -> {
|
||||
@ -475,16 +440,12 @@ class SignupViewModel(
|
||||
is DataState.Error -> {
|
||||
deleteUser(userId)
|
||||
deleteClient(clientId)
|
||||
val message = result.exception.message.toString()
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = SignUpDialog.Error(message))
|
||||
}
|
||||
DialogManager.showMessage(result.exception.toDialogMessage())
|
||||
}
|
||||
|
||||
is DataState.Success -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = null)
|
||||
}
|
||||
DialogManager.dismissDialog()
|
||||
|
||||
sendEvent(SignUpEvent.ShowToast("Registration successful."))
|
||||
sendAction(
|
||||
SignUpAction.Internal.ReceiveRegisterResult(
|
||||
@ -509,6 +470,20 @@ class SignupViewModel(
|
||||
clientRepository.deleteClient(clientId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadCountriesFromJson() {
|
||||
viewModelScope.launch {
|
||||
when (val countriesWithStatesResult = assetRepository.getCountriesWithStates()) {
|
||||
is DataState.Success ->
|
||||
mutableStateFlow.update {
|
||||
it.copy(countriesWithStates = countriesWithStatesResult.data)
|
||||
}
|
||||
|
||||
is DataState.Error -> Logger.d("Failed to load countries.json: ${countriesWithStatesResult.exception.message}")
|
||||
is DataState.Loading -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
@ -527,36 +502,13 @@ data class SignUpState(
|
||||
val stateInput: String = "",
|
||||
val countryInput: String = "",
|
||||
val businessNameInput: String = "",
|
||||
val dialogState: SignUpDialog? = null,
|
||||
val passwordStrengthState: PasswordStrengthState = PasswordStrengthState.NONE,
|
||||
val passwordError: String? = null,
|
||||
val passwordFeedback: ImmutableList<String> = persistentListOf(),
|
||||
val countriesWithStates: Map<String, List<String>> = emptyMap(),
|
||||
) : Parcelable {
|
||||
@IgnoredOnParcel
|
||||
val isPasswordStrong: Boolean
|
||||
get() = when (passwordStrengthState) {
|
||||
PasswordStrengthState.NONE,
|
||||
PasswordStrengthState.WEAK_1,
|
||||
PasswordStrengthState.WEAK_2,
|
||||
PasswordStrengthState.WEAK_3,
|
||||
-> false
|
||||
|
||||
PasswordStrengthState.GOOD,
|
||||
PasswordStrengthState.STRONG,
|
||||
PasswordStrengthState.VERY_STRONG,
|
||||
-> true
|
||||
}
|
||||
|
||||
@IgnoredOnParcel
|
||||
val isPasswordMatch: Boolean
|
||||
get() = passwordInput == confirmPasswordInput
|
||||
}
|
||||
|
||||
sealed interface SignUpDialog : Parcelable {
|
||||
@Parcelize
|
||||
data object Loading : SignUpDialog
|
||||
|
||||
@Parcelize
|
||||
data class Error(val message: String) : SignUpDialog
|
||||
val statesForSelectedCountry =
|
||||
countriesWithStates[countryInput]
|
||||
}
|
||||
|
||||
sealed interface SignUpEvent {
|
||||
@ -584,6 +536,7 @@ sealed interface SignUpAction {
|
||||
data object SubmitClick : SignUpAction
|
||||
data object CloseClick : SignUpAction
|
||||
data object ErrorDialogDismiss : SignUpAction
|
||||
data object LoadCountries : SignUpAction
|
||||
|
||||
sealed class Internal : SignUpAction {
|
||||
data class ReceiveRegisterResult(
|
||||
|
||||
@ -914,6 +914,152 @@
|
||||
| | | | \--- io.insert-koin:koin-annotations-jvm:1.4.0-RC4
|
||||
| | | | \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.24 -> 2.1.0 (*)
|
||||
| | | +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0 (*)
|
||||
| | | +--- org.jetbrains.compose.components:components-resources:1.7.0-rc01
|
||||
| | | | \--- org.jetbrains.compose.components:components-resources-android:1.7.0-rc01
|
||||
| | | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.9.23 -> 2.1.0 (*)
|
||||
| | | | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | +--- org.jetbrains.compose.foundation:foundation:1.7.0-rc01
|
||||
| | | | | +--- androidx.compose.foundation:foundation:1.7.1 -> 1.7.6
|
||||
| | | | | | \--- androidx.compose.foundation:foundation-android:1.7.6
|
||||
| | | | | | +--- androidx.annotation:annotation:1.1.0 -> 1.8.1 (*)
|
||||
| | | | | | +--- androidx.annotation:annotation-experimental:1.4.0 -> 1.4.1 (*)
|
||||
| | | | | | +--- androidx.collection:collection:1.4.0 -> 1.4.4 (*)
|
||||
| | | | | | +--- androidx.compose.animation:animation:1.7.6
|
||||
| | | | | | | \--- androidx.compose.animation:animation-android:1.7.6
|
||||
| | | | | | | +--- androidx.annotation:annotation:1.1.0 -> 1.8.1 (*)
|
||||
| | | | | | | +--- androidx.annotation:annotation-experimental:1.4.0 -> 1.4.1 (*)
|
||||
| | | | | | | +--- androidx.collection:collection:1.4.0 -> 1.4.4 (*)
|
||||
| | | | | | | +--- androidx.compose.animation:animation-core:1.7.6
|
||||
| | | | | | | | \--- androidx.compose.animation:animation-core-android:1.7.6
|
||||
| | | | | | | | +--- androidx.annotation:annotation:1.1.0 -> 1.8.1 (*)
|
||||
| | | | | | | | +--- androidx.collection:collection:1.4.0 -> 1.4.4 (*)
|
||||
| | | | | | | | +--- androidx.compose.runtime:runtime:1.7.6 (*)
|
||||
| | | | | | | | +--- androidx.compose.ui:ui:1.7.6 (*)
|
||||
| | | | | | | | +--- androidx.compose.ui:ui-graphics:1.7.6 (*)
|
||||
| | | | | | | | +--- androidx.compose.ui:ui-unit:1.7.6 (*)
|
||||
| | | | | | | | +--- androidx.compose.ui:ui-util:1.7.6 (*)
|
||||
| | | | | | | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.1.0 (*)
|
||||
| | | | | | | | +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3 -> 1.9.0 (*)
|
||||
| | | | | | | | \--- androidx.compose.animation:animation:1.7.6 (c)
|
||||
| | | | | | | +--- androidx.compose.foundation:foundation-layout:1.7.6
|
||||
| | | | | | | | \--- androidx.compose.foundation:foundation-layout-android:1.7.6
|
||||
| | | | | | | | +--- androidx.annotation:annotation:1.1.0 -> 1.8.1 (*)
|
||||
| | | | | | | | +--- androidx.annotation:annotation-experimental:1.4.0 -> 1.4.1 (*)
|
||||
| | | | | | | | +--- androidx.collection:collection:1.4.0 -> 1.4.4 (*)
|
||||
| | | | | | | | +--- androidx.compose.animation:animation-core:1.2.1 -> 1.7.6 (*)
|
||||
| | | | | | | | +--- androidx.compose.runtime:runtime:1.7.6 (*)
|
||||
| | | | | | | | +--- androidx.compose.ui:ui:1.7.6 (*)
|
||||
| | | | | | | | +--- androidx.compose.ui:ui-unit:1.7.6 (*)
|
||||
| | | | | | | | +--- androidx.compose.ui:ui-util:1.7.6 (*)
|
||||
| | | | | | | | +--- androidx.core:core:1.7.0 -> 1.15.0 (*)
|
||||
| | | | | | | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.1.0 (*)
|
||||
| | | | | | | | \--- androidx.compose.foundation:foundation:1.7.6 (c)
|
||||
| | | | | | | +--- androidx.compose.runtime:runtime:1.7.6 (*)
|
||||
| | | | | | | +--- androidx.compose.ui:ui:1.7.6 (*)
|
||||
| | | | | | | +--- androidx.compose.ui:ui-geometry:1.7.6 (*)
|
||||
| | | | | | | +--- androidx.compose.ui:ui-graphics:1.7.6 (*)
|
||||
| | | | | | | +--- androidx.compose.ui:ui-util:1.7.6 (*)
|
||||
| | | | | | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.1.0 (*)
|
||||
| | | | | | | \--- androidx.compose.animation:animation-core:1.7.6 (c)
|
||||
| | | | | | +--- androidx.compose.foundation:foundation-layout:1.7.6 (*)
|
||||
| | | | | | +--- androidx.compose.runtime:runtime:1.7.6 (*)
|
||||
| | | | | | +--- androidx.compose.ui:ui:1.7.6 (*)
|
||||
| | | | | | +--- androidx.compose.ui:ui-text:1.7.6 (*)
|
||||
| | | | | | +--- androidx.compose.ui:ui-util:1.7.6 (*)
|
||||
| | | | | | +--- androidx.core:core:1.13.1 -> 1.15.0 (*)
|
||||
| | | | | | +--- androidx.emoji2:emoji2:1.3.0 (*)
|
||||
| | | | | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.1.0 (*)
|
||||
| | | | | | \--- androidx.compose.foundation:foundation-layout:1.7.6 (c)
|
||||
| | | | | +--- org.jetbrains.compose.animation:animation:1.7.0-rc01
|
||||
| | | | | | +--- androidx.compose.animation:animation:1.7.1 -> 1.7.6 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.animation:animation-core:1.7.0-rc01
|
||||
| | | | | | | +--- androidx.compose.animation:animation-core:1.7.1 -> 1.7.6 (*)
|
||||
| | | | | | | +--- org.jetbrains.compose.annotation-internal:annotation:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | | +--- org.jetbrains.compose.collection-internal:collection:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | | +--- org.jetbrains.compose.ui:ui:1.7.0-rc01
|
||||
| | | | | | | | +--- androidx.compose.ui:ui:1.7.1 -> 1.7.6 (*)
|
||||
| | | | | | | | +--- org.jetbrains.androidx.lifecycle:lifecycle-common:2.8.3-rc01 -> 2.8.3 (*)
|
||||
| | | | | | | | +--- org.jetbrains.androidx.lifecycle:lifecycle-runtime:2.8.3-rc01
|
||||
| | | | | | | | | +--- androidx.arch.core:core-common:2.2.0 (*)
|
||||
| | | | | | | | | +--- androidx.lifecycle:lifecycle-runtime:2.8.5 -> 2.8.7 (*)
|
||||
| | | | | | | | | +--- org.jetbrains.androidx.lifecycle:lifecycle-common:2.8.3-rc01 -> 2.8.3 (*)
|
||||
| | | | | | | | | +--- org.jetbrains.compose.annotation-internal:annotation:1.6.11 -> 1.7.0 (*)
|
||||
| | | | | | | | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.9.24 -> 2.1.0 (*)
|
||||
| | | | | | | | | \--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0 -> 1.9.0 (*)
|
||||
| | | | | | | | +--- org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose:2.8.3-rc01
|
||||
| | | | | | | | | +--- androidx.lifecycle:lifecycle-runtime-compose:2.8.5 -> 2.8.7 (*)
|
||||
| | | | | | | | | +--- org.jetbrains.androidx.lifecycle:lifecycle-common:2.8.3-rc01 -> 2.8.3 (*)
|
||||
| | | | | | | | | +--- org.jetbrains.androidx.lifecycle:lifecycle-runtime:2.8.3-rc01 (*)
|
||||
| | | | | | | | | +--- org.jetbrains.compose.annotation-internal:annotation:1.6.11 -> 1.7.0 (*)
|
||||
| | | | | | | | | +--- org.jetbrains.compose.runtime:runtime:1.6.11 -> 1.7.0 (*)
|
||||
| | | | | | | | | \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.24 -> 2.1.0 (*)
|
||||
| | | | | | | | +--- org.jetbrains.androidx.lifecycle:lifecycle-viewmodel:2.8.3-rc01 -> 2.8.3 (*)
|
||||
| | | | | | | | +--- org.jetbrains.compose.annotation-internal:annotation:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | | | +--- org.jetbrains.compose.collection-internal:collection:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | | | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | | | +--- org.jetbrains.compose.runtime:runtime-saveable:1.7.0-rc01
|
||||
| | | | | | | | | +--- androidx.compose.runtime:runtime-saveable:1.7.1 -> 1.7.6 (*)
|
||||
| | | | | | | | | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | | | | \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.9.24 -> 2.1.0 (*)
|
||||
| | | | | | | | +--- org.jetbrains.compose.ui:ui-geometry:1.7.0-rc01
|
||||
| | | | | | | | | +--- androidx.compose.ui:ui-geometry:1.7.1 -> 1.7.6 (*)
|
||||
| | | | | | | | | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | | | | \--- org.jetbrains.compose.ui:ui-util:1.7.0-rc01
|
||||
| | | | | | | | | \--- androidx.compose.ui:ui-util:1.7.1 -> 1.7.6 (*)
|
||||
| | | | | | | | +--- org.jetbrains.compose.ui:ui-graphics:1.7.0-rc01
|
||||
| | | | | | | | | +--- androidx.compose.ui:ui-graphics:1.7.1 -> 1.7.6 (*)
|
||||
| | | | | | | | | +--- org.jetbrains.compose.annotation-internal:annotation:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | | | | +--- org.jetbrains.compose.collection-internal:collection:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | | | | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | | | | +--- org.jetbrains.compose.ui:ui-geometry:1.7.0-rc01 (*)
|
||||
| | | | | | | | | +--- org.jetbrains.compose.ui:ui-unit:1.7.0-rc01
|
||||
| | | | | | | | | | +--- androidx.compose.ui:ui-unit:1.7.1 -> 1.7.6 (*)
|
||||
| | | | | | | | | | +--- org.jetbrains.compose.annotation-internal:annotation:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | | | | | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | | | | | +--- org.jetbrains.compose.ui:ui-geometry:1.7.0-rc01 (*)
|
||||
| | | | | | | | | | \--- org.jetbrains.compose.ui:ui-util:1.7.0-rc01 (*)
|
||||
| | | | | | | | | \--- org.jetbrains.compose.ui:ui-util:1.7.0-rc01 (*)
|
||||
| | | | | | | | +--- org.jetbrains.compose.ui:ui-text:1.7.0-rc01
|
||||
| | | | | | | | | +--- androidx.compose.ui:ui-text:1.7.1 -> 1.7.6 (*)
|
||||
| | | | | | | | | +--- org.jetbrains.compose.annotation-internal:annotation:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | | | | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | | | | +--- org.jetbrains.compose.runtime:runtime-saveable:1.7.0-rc01 (*)
|
||||
| | | | | | | | | +--- org.jetbrains.compose.ui:ui-geometry:1.7.0-rc01 (*)
|
||||
| | | | | | | | | +--- org.jetbrains.compose.ui:ui-graphics:1.7.0-rc01 (*)
|
||||
| | | | | | | | | +--- org.jetbrains.compose.ui:ui-unit:1.7.0-rc01 (*)
|
||||
| | | | | | | | | +--- org.jetbrains.compose.ui:ui-util:1.7.0-rc01 (*)
|
||||
| | | | | | | | | +--- org.jetbrains.kotlinx:atomicfu:0.23.2 (*)
|
||||
| | | | | | | | | \--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0 -> 1.9.0 (*)
|
||||
| | | | | | | | +--- org.jetbrains.compose.ui:ui-unit:1.7.0-rc01 (*)
|
||||
| | | | | | | | +--- org.jetbrains.compose.ui:ui-util:1.7.0-rc01 (*)
|
||||
| | | | | | | | +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.9.24 -> 2.1.0 (*)
|
||||
| | | | | | | | +--- org.jetbrains.kotlinx:atomicfu:0.23.2 (*)
|
||||
| | | | | | | | \--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0 -> 1.9.0 (*)
|
||||
| | | | | | | +--- org.jetbrains.compose.ui:ui-unit:1.7.0-rc01 (*)
|
||||
| | | | | | | +--- org.jetbrains.compose.ui:ui-util:1.7.0-rc01 (*)
|
||||
| | | | | | | \--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0 -> 1.9.0 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.collection-internal:collection:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.foundation:foundation-layout:1.7.0-rc01
|
||||
| | | | | | | +--- androidx.compose.foundation:foundation-layout:1.7.1 -> 1.7.6 (*)
|
||||
| | | | | | | +--- org.jetbrains.compose.annotation-internal:annotation:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | | +--- org.jetbrains.compose.collection-internal:collection:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | | +--- org.jetbrains.compose.ui:ui:1.7.0-rc01 (*)
|
||||
| | | | | | | \--- org.jetbrains.compose.ui:ui-util:1.7.0-rc01 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.ui:ui:1.7.0-rc01 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.ui:ui-geometry:1.7.0-rc01 (*)
|
||||
| | | | | | \--- org.jetbrains.compose.ui:ui-util:1.7.0-rc01 (*)
|
||||
| | | | | +--- org.jetbrains.compose.annotation-internal:annotation:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | +--- org.jetbrains.compose.collection-internal:collection:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | +--- org.jetbrains.compose.foundation:foundation-layout:1.7.0-rc01 (*)
|
||||
| | | | | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | +--- org.jetbrains.compose.ui:ui:1.7.0-rc01 (*)
|
||||
| | | | | +--- org.jetbrains.compose.ui:ui-text:1.7.0-rc01 (*)
|
||||
| | | | | \--- org.jetbrains.compose.ui:ui-util:1.7.0-rc01 (*)
|
||||
| | | | \--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0 -> 1.9.0 (*)
|
||||
| | | +--- org.jetbrains.compose.runtime:runtime:1.7.0 (*)
|
||||
| | | \--- org.jetbrains.kotlin:kotlin-parcelize-runtime:2.1.0
|
||||
| | | +--- org.jetbrains.kotlin:kotlin-stdlib:2.1.0 (*)
|
||||
| | | \--- org.jetbrains.kotlin:kotlin-android-extensions-runtime:2.1.0
|
||||
@ -1185,6 +1331,8 @@
|
||||
| | | +--- io.insert-koin:koin-annotations:1.4.0-RC4 (*)
|
||||
| | | \--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | +--- org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3 (*)
|
||||
| | +--- org.jetbrains.compose.runtime:runtime:1.7.0 (*)
|
||||
| | +--- org.jetbrains.compose.components:components-resources:1.7.0-rc01 (*)
|
||||
| | \--- org.jetbrains.kotlin:kotlin-parcelize-runtime:2.1.0 (*)
|
||||
| +--- project :core:network (*)
|
||||
| +--- org.jetbrains.kotlin:kotlin-stdlib:2.1.0 (*)
|
||||
@ -1206,57 +1354,7 @@
|
||||
| | +--- androidx.compose.runtime:runtime -> 1.7.6 (*)
|
||||
| | +--- com.google.accompanist:accompanist-pager:0.34.0
|
||||
| | | +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4 -> 1.9.0 (*)
|
||||
| | | +--- androidx.compose.foundation:foundation:1.6.0 -> 1.7.6
|
||||
| | | | \--- androidx.compose.foundation:foundation-android:1.7.6
|
||||
| | | | +--- androidx.annotation:annotation:1.1.0 -> 1.8.1 (*)
|
||||
| | | | +--- androidx.annotation:annotation-experimental:1.4.0 -> 1.4.1 (*)
|
||||
| | | | +--- androidx.collection:collection:1.4.0 -> 1.4.4 (*)
|
||||
| | | | +--- androidx.compose.animation:animation:1.7.6
|
||||
| | | | | \--- androidx.compose.animation:animation-android:1.7.6
|
||||
| | | | | +--- androidx.annotation:annotation:1.1.0 -> 1.8.1 (*)
|
||||
| | | | | +--- androidx.annotation:annotation-experimental:1.4.0 -> 1.4.1 (*)
|
||||
| | | | | +--- androidx.collection:collection:1.4.0 -> 1.4.4 (*)
|
||||
| | | | | +--- androidx.compose.animation:animation-core:1.7.6
|
||||
| | | | | | \--- androidx.compose.animation:animation-core-android:1.7.6
|
||||
| | | | | | +--- androidx.annotation:annotation:1.1.0 -> 1.8.1 (*)
|
||||
| | | | | | +--- androidx.collection:collection:1.4.0 -> 1.4.4 (*)
|
||||
| | | | | | +--- androidx.compose.runtime:runtime:1.7.6 (*)
|
||||
| | | | | | +--- androidx.compose.ui:ui:1.7.6 (*)
|
||||
| | | | | | +--- androidx.compose.ui:ui-graphics:1.7.6 (*)
|
||||
| | | | | | +--- androidx.compose.ui:ui-unit:1.7.6 (*)
|
||||
| | | | | | +--- androidx.compose.ui:ui-util:1.7.6 (*)
|
||||
| | | | | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.1.0 (*)
|
||||
| | | | | | +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3 -> 1.9.0 (*)
|
||||
| | | | | | \--- androidx.compose.animation:animation:1.7.6 (c)
|
||||
| | | | | +--- androidx.compose.foundation:foundation-layout:1.7.6
|
||||
| | | | | | \--- androidx.compose.foundation:foundation-layout-android:1.7.6
|
||||
| | | | | | +--- androidx.annotation:annotation:1.1.0 -> 1.8.1 (*)
|
||||
| | | | | | +--- androidx.annotation:annotation-experimental:1.4.0 -> 1.4.1 (*)
|
||||
| | | | | | +--- androidx.collection:collection:1.4.0 -> 1.4.4 (*)
|
||||
| | | | | | +--- androidx.compose.animation:animation-core:1.2.1 -> 1.7.6 (*)
|
||||
| | | | | | +--- androidx.compose.runtime:runtime:1.7.6 (*)
|
||||
| | | | | | +--- androidx.compose.ui:ui:1.7.6 (*)
|
||||
| | | | | | +--- androidx.compose.ui:ui-unit:1.7.6 (*)
|
||||
| | | | | | +--- androidx.compose.ui:ui-util:1.7.6 (*)
|
||||
| | | | | | +--- androidx.core:core:1.7.0 -> 1.15.0 (*)
|
||||
| | | | | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.1.0 (*)
|
||||
| | | | | | \--- androidx.compose.foundation:foundation:1.7.6 (c)
|
||||
| | | | | +--- androidx.compose.runtime:runtime:1.7.6 (*)
|
||||
| | | | | +--- androidx.compose.ui:ui:1.7.6 (*)
|
||||
| | | | | +--- androidx.compose.ui:ui-geometry:1.7.6 (*)
|
||||
| | | | | +--- androidx.compose.ui:ui-graphics:1.7.6 (*)
|
||||
| | | | | +--- androidx.compose.ui:ui-util:1.7.6 (*)
|
||||
| | | | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.1.0 (*)
|
||||
| | | | | \--- androidx.compose.animation:animation-core:1.7.6 (c)
|
||||
| | | | +--- androidx.compose.foundation:foundation-layout:1.7.6 (*)
|
||||
| | | | +--- androidx.compose.runtime:runtime:1.7.6 (*)
|
||||
| | | | +--- androidx.compose.ui:ui:1.7.6 (*)
|
||||
| | | | +--- androidx.compose.ui:ui-text:1.7.6 (*)
|
||||
| | | | +--- androidx.compose.ui:ui-util:1.7.6 (*)
|
||||
| | | | +--- androidx.core:core:1.13.1 -> 1.15.0 (*)
|
||||
| | | | +--- androidx.emoji2:emoji2:1.3.0 (*)
|
||||
| | | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.1.0 (*)
|
||||
| | | | \--- androidx.compose.foundation:foundation-layout:1.7.6 (c)
|
||||
| | | +--- androidx.compose.foundation:foundation:1.6.0 -> 1.7.6 (*)
|
||||
| | | +--- dev.chrisbanes.snapper:snapper:0.2.2
|
||||
| | | | +--- androidx.compose.foundation:foundation:1.1.1 -> 1.7.6 (*)
|
||||
| | | | \--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10 -> 1.9.20 (*)
|
||||
@ -1283,65 +1381,7 @@
|
||||
| | | +--- dev.chrisbanes.material3:material3-window-size-class-multiplatform:0.5.0
|
||||
| | | | \--- dev.chrisbanes.material3:material3-window-size-class-multiplatform-android:0.5.0
|
||||
| | | | +--- androidx.window:window:1.2.0 -> 1.3.0 (*)
|
||||
| | | | +--- org.jetbrains.compose.ui:ui:1.6.0 -> 1.7.0-rc01
|
||||
| | | | | +--- androidx.compose.ui:ui:1.7.1 -> 1.7.6 (*)
|
||||
| | | | | +--- org.jetbrains.androidx.lifecycle:lifecycle-common:2.8.3-rc01 -> 2.8.3 (*)
|
||||
| | | | | +--- org.jetbrains.androidx.lifecycle:lifecycle-runtime:2.8.3-rc01
|
||||
| | | | | | +--- androidx.arch.core:core-common:2.2.0 (*)
|
||||
| | | | | | +--- androidx.lifecycle:lifecycle-runtime:2.8.5 -> 2.8.7 (*)
|
||||
| | | | | | +--- org.jetbrains.androidx.lifecycle:lifecycle-common:2.8.3-rc01 -> 2.8.3 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.annotation-internal:annotation:1.6.11 -> 1.7.0 (*)
|
||||
| | | | | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.9.24 -> 2.1.0 (*)
|
||||
| | | | | | \--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0 -> 1.9.0 (*)
|
||||
| | | | | +--- org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose:2.8.3-rc01
|
||||
| | | | | | +--- androidx.lifecycle:lifecycle-runtime-compose:2.8.5 -> 2.8.7 (*)
|
||||
| | | | | | +--- org.jetbrains.androidx.lifecycle:lifecycle-common:2.8.3-rc01 -> 2.8.3 (*)
|
||||
| | | | | | +--- org.jetbrains.androidx.lifecycle:lifecycle-runtime:2.8.3-rc01 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.annotation-internal:annotation:1.6.11 -> 1.7.0 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.runtime:runtime:1.6.11 -> 1.7.0 (*)
|
||||
| | | | | | \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.24 -> 2.1.0 (*)
|
||||
| | | | | +--- org.jetbrains.androidx.lifecycle:lifecycle-viewmodel:2.8.3-rc01 -> 2.8.3 (*)
|
||||
| | | | | +--- org.jetbrains.compose.annotation-internal:annotation:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | +--- org.jetbrains.compose.collection-internal:collection:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | +--- org.jetbrains.compose.runtime:runtime-saveable:1.7.0-rc01
|
||||
| | | | | | +--- androidx.compose.runtime:runtime-saveable:1.7.1 -> 1.7.6 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.9.24 -> 2.1.0 (*)
|
||||
| | | | | +--- org.jetbrains.compose.ui:ui-geometry:1.7.0-rc01
|
||||
| | | | | | +--- androidx.compose.ui:ui-geometry:1.7.1 -> 1.7.6 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | \--- org.jetbrains.compose.ui:ui-util:1.7.0-rc01
|
||||
| | | | | | \--- androidx.compose.ui:ui-util:1.7.1 -> 1.7.6 (*)
|
||||
| | | | | +--- org.jetbrains.compose.ui:ui-graphics:1.7.0-rc01
|
||||
| | | | | | +--- androidx.compose.ui:ui-graphics:1.7.1 -> 1.7.6 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.annotation-internal:annotation:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.collection-internal:collection:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.ui:ui-geometry:1.7.0-rc01 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.ui:ui-unit:1.7.0-rc01
|
||||
| | | | | | | +--- androidx.compose.ui:ui-unit:1.7.1 -> 1.7.6 (*)
|
||||
| | | | | | | +--- org.jetbrains.compose.annotation-internal:annotation:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | | +--- org.jetbrains.compose.ui:ui-geometry:1.7.0-rc01 (*)
|
||||
| | | | | | | \--- org.jetbrains.compose.ui:ui-util:1.7.0-rc01 (*)
|
||||
| | | | | | \--- org.jetbrains.compose.ui:ui-util:1.7.0-rc01 (*)
|
||||
| | | | | +--- org.jetbrains.compose.ui:ui-text:1.7.0-rc01
|
||||
| | | | | | +--- androidx.compose.ui:ui-text:1.7.1 -> 1.7.6 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.annotation-internal:annotation:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.runtime:runtime-saveable:1.7.0-rc01 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.ui:ui-geometry:1.7.0-rc01 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.ui:ui-graphics:1.7.0-rc01 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.ui:ui-unit:1.7.0-rc01 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.ui:ui-util:1.7.0-rc01 (*)
|
||||
| | | | | | +--- org.jetbrains.kotlinx:atomicfu:0.23.2 (*)
|
||||
| | | | | | \--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0 -> 1.9.0 (*)
|
||||
| | | | | +--- org.jetbrains.compose.ui:ui-unit:1.7.0-rc01 (*)
|
||||
| | | | | +--- org.jetbrains.compose.ui:ui-util:1.7.0-rc01 (*)
|
||||
| | | | | +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.9.24 -> 2.1.0 (*)
|
||||
| | | | | +--- org.jetbrains.kotlinx:atomicfu:0.23.2 (*)
|
||||
| | | | | \--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0 -> 1.9.0 (*)
|
||||
| | | | +--- org.jetbrains.compose.ui:ui:1.6.0 -> 1.7.0-rc01 (*)
|
||||
| | | | \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.22 -> 2.1.0 (*)
|
||||
| | | +--- org.jetbrains.kotlin:kotlin-stdlib:2.1.0 (*)
|
||||
| | | +--- io.insert-koin:koin-bom:4.0.1-RC1 (*)
|
||||
@ -1354,38 +1394,7 @@
|
||||
| | | | | +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4 -> 1.9.0 (*)
|
||||
| | | | | \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.22 -> 2.1.0 (*)
|
||||
| | | | +--- io.coil-kt.coil3:coil-core:3.0.0-alpha10 (*)
|
||||
| | | | +--- org.jetbrains.compose.foundation:foundation:1.6.11 -> 1.7.0-rc01
|
||||
| | | | | +--- androidx.compose.foundation:foundation:1.7.1 -> 1.7.6 (*)
|
||||
| | | | | +--- org.jetbrains.compose.animation:animation:1.7.0-rc01
|
||||
| | | | | | +--- androidx.compose.animation:animation:1.7.1 -> 1.7.6 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.animation:animation-core:1.7.0-rc01
|
||||
| | | | | | | +--- androidx.compose.animation:animation-core:1.7.1 -> 1.7.6 (*)
|
||||
| | | | | | | +--- org.jetbrains.compose.annotation-internal:annotation:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | | +--- org.jetbrains.compose.collection-internal:collection:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | | +--- org.jetbrains.compose.ui:ui:1.7.0-rc01 (*)
|
||||
| | | | | | | +--- org.jetbrains.compose.ui:ui-unit:1.7.0-rc01 (*)
|
||||
| | | | | | | +--- org.jetbrains.compose.ui:ui-util:1.7.0-rc01 (*)
|
||||
| | | | | | | \--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0 -> 1.9.0 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.collection-internal:collection:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.foundation:foundation-layout:1.7.0-rc01
|
||||
| | | | | | | +--- androidx.compose.foundation:foundation-layout:1.7.1 -> 1.7.6 (*)
|
||||
| | | | | | | +--- org.jetbrains.compose.annotation-internal:annotation:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | | +--- org.jetbrains.compose.collection-internal:collection:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | | +--- org.jetbrains.compose.ui:ui:1.7.0-rc01 (*)
|
||||
| | | | | | | \--- org.jetbrains.compose.ui:ui-util:1.7.0-rc01 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.ui:ui:1.7.0-rc01 (*)
|
||||
| | | | | | +--- org.jetbrains.compose.ui:ui-geometry:1.7.0-rc01 (*)
|
||||
| | | | | | \--- org.jetbrains.compose.ui:ui-util:1.7.0-rc01 (*)
|
||||
| | | | | +--- org.jetbrains.compose.annotation-internal:annotation:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | +--- org.jetbrains.compose.collection-internal:collection:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | +--- org.jetbrains.compose.foundation:foundation-layout:1.7.0-rc01 (*)
|
||||
| | | | | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | | +--- org.jetbrains.compose.ui:ui:1.7.0-rc01 (*)
|
||||
| | | | | +--- org.jetbrains.compose.ui:ui-text:1.7.0-rc01 (*)
|
||||
| | | | | \--- org.jetbrains.compose.ui:ui-util:1.7.0-rc01 (*)
|
||||
| | | | +--- org.jetbrains.compose.foundation:foundation:1.6.11 -> 1.7.0-rc01 (*)
|
||||
| | | | \--- org.jetbrains.kotlin:kotlin-stdlib:2.0.10 -> 2.1.0 (*)
|
||||
| | | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | +--- org.jetbrains.compose.foundation:foundation:1.7.0-rc01 (*)
|
||||
@ -1446,12 +1455,7 @@
|
||||
| | | | \--- org.jetbrains.compose.ui:ui-graphics:1.7.0-rc01 (*)
|
||||
| | | +--- org.jetbrains.compose.ui:ui:1.7.0-rc01 (*)
|
||||
| | | +--- org.jetbrains.compose.ui:ui-util:1.7.0-rc01 (*)
|
||||
| | | +--- org.jetbrains.compose.components:components-resources:1.7.0-rc01
|
||||
| | | | \--- org.jetbrains.compose.components:components-resources-android:1.7.0-rc01
|
||||
| | | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.9.23 -> 2.1.0 (*)
|
||||
| | | | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 -> 1.7.0 (*)
|
||||
| | | | +--- org.jetbrains.compose.foundation:foundation:1.7.0-rc01 (*)
|
||||
| | | | \--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0 -> 1.9.0 (*)
|
||||
| | | +--- org.jetbrains.compose.components:components-resources:1.7.0-rc01 (*)
|
||||
| | | \--- org.jetbrains.compose.components:components-ui-tooling-preview:1.7.0-rc01
|
||||
| | | \--- org.jetbrains.compose.components:components-ui-tooling-preview-android:1.7.0-rc01
|
||||
| | | \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.23 -> 2.1.0 (*)
|
||||
@ -1656,6 +1660,7 @@
|
||||
| | +--- org.jetbrains.kotlin:kotlin-stdlib:2.1.0 (*)
|
||||
| | +--- org.jetbrains.kotlin:kotlin-reflect:2.1.0
|
||||
| | | \--- org.jetbrains.kotlin:kotlin-stdlib:2.1.0 (*)
|
||||
| | +--- org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3 (*)
|
||||
| | \--- org.jetbrains.kotlin:kotlin-parcelize-runtime:2.1.0 (*)
|
||||
| +--- project :libs:mifos-passcode
|
||||
| | +--- androidx.lifecycle:lifecycle-runtime-compose:2.8.7 (*)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package: name='org.mifospay' versionCode='1' versionName='2025.4.4-beta.0.0' platformBuildVersionName='15' platformBuildVersionCode='35' compileSdkVersion='35' compileSdkVersionCodename='15'
|
||||
package: name='org.mifospay' versionCode='1' versionName='2025.6.4-beta.0.10' platformBuildVersionName='15' platformBuildVersionCode='35' compileSdkVersion='35' compileSdkVersionCodename='15'
|
||||
sdkVersion:'26'
|
||||
targetSdkVersion:'34'
|
||||
uses-permission: name='android.permission.INTERNET'
|
||||
|
||||
Loading…
Reference in New Issue
Block a user