From 114ae9b975f456445fe125b37aa8d0ab9333e2fc Mon Sep 17 00:00:00 2001 From: rohitlokhande Date: Mon, 7 Apr 2025 01:19:03 +0530 Subject: [PATCH 1/7] feat: Add documentation comments for AccountViewModel and related classes --- .run/mifospay-ios.run.xml | 2 +- build.gradle.kts | 1 + feature/accounts/build.gradle.kts | 20 ++++++++ .../feature/accounts/AccountViewModel.kt | 46 +++++++++++++++++-- feature/auth/build.gradle.kts | 20 ++++++++ gradle.properties | 5 +- gradle/libs.versions.toml | 1 + 7 files changed, 87 insertions(+), 8 deletions(-) diff --git a/.run/mifospay-ios.run.xml b/.run/mifospay-ios.run.xml index 8b3ac7d5d..db9a15f8a 100644 --- a/.run/mifospay-ios.run.xml +++ b/.run/mifospay-ios.run.xml @@ -1,5 +1,5 @@ - + diff --git a/build.gradle.kts b/build.gradle.kts index 6f2e94a62..d70a8f6ed 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -33,6 +33,7 @@ plugins { alias(libs.plugins.kotlinMultiplatform) apply false alias(libs.plugins.wire) apply false alias(libs.plugins.ktorfit) apply false + alias(libs.plugins.dokka) } object DynamicVersion { diff --git a/feature/accounts/build.gradle.kts b/feature/accounts/build.gradle.kts index d4a4a02b3..62cc9f631 100644 --- a/feature/accounts/build.gradle.kts +++ b/feature/accounts/build.gradle.kts @@ -10,6 +10,7 @@ plugins { alias(libs.plugins.mifospay.cmp.feature) alias(libs.plugins.kotlin.parcelize) + alias(libs.plugins.dokka) } android { @@ -27,4 +28,23 @@ kotlin { implementation(libs.kotlinx.serialization.json) } } +} +// Configure Dokka +tasks.withType().configureEach { + // Output format (HTML by default) + outputDirectory.set(layout.buildDirectory.dir("dokka")) + + // Module name in documentation + moduleName.set("feature") + + // Documentation format + dokkaSourceSets { + configureEach { + sourceLink { + localDirectory.set(file("src")) + remoteUrl.set(uri("https://github.com/openMF/mobile-wallet.git").toURL()) + remoteLineSuffix.set("#L") + } + } + } } \ No newline at end of file diff --git a/feature/accounts/src/commonMain/kotlin/org/mifospay/feature/accounts/AccountViewModel.kt b/feature/accounts/src/commonMain/kotlin/org/mifospay/feature/accounts/AccountViewModel.kt index 285f1a0cf..17925863f 100644 --- a/feature/accounts/src/commonMain/kotlin/org/mifospay/feature/accounts/AccountViewModel.kt +++ b/feature/accounts/src/commonMain/kotlin/org/mifospay/feature/accounts/AccountViewModel.kt @@ -34,7 +34,6 @@ import org.mifospay.feature.accounts.AccountAction.Internal.DeleteBeneficiary import org.mifospay.feature.accounts.AccountEvent.OnAddEditSavingsAccount import org.mifospay.feature.accounts.beneficiary.BeneficiaryAddEditType import org.mifospay.feature.accounts.savingsaccount.SavingsAddEditType - @OptIn(ExperimentalCoroutinesApi::class) class AccountViewModel( private val userRepository: UserPreferencesRepository, @@ -51,6 +50,9 @@ class AccountViewModel( ) }, ) { + /** + * Exposes the account and beneficiary list as a [StateFlow] of [AccountState.ViewState]. + */ val accountState = repository.getAccountAndBeneficiaryList(state.clientId) .mapLatest { when (it) { @@ -67,6 +69,9 @@ class AccountViewModel( initialValue = AccountState.ViewState.Loading, ) + /** + * Handles [AccountAction] and triggers corresponding [AccountEvent] or state updates. + */ override fun handleAction(action: AccountAction) { when (action) { is AccountAction.CreateSavingsAccount -> { @@ -124,6 +129,9 @@ class AccountViewModel( } } + /** + * Handles updating the default account in [userRepository]. + */ private fun handleSetDefaultAccount(action: AccountAction.SetDefaultAccount) { viewModelScope.launch { userRepository.updateDefaultAccount( @@ -138,6 +146,9 @@ class AccountViewModel( sendEvent(AccountEvent.ShowToast("Default account updated")) } + /** + * Sends a delete request for the given beneficiary ID and shows a loading dialog. + */ private fun handleDeleteBeneficiary(action: DeleteBeneficiary) { mutableStateFlow.update { it.copy(dialogState = AccountState.DialogState.Loading) } @@ -148,6 +159,9 @@ class AccountViewModel( } } + /** + * Handles the result of deleting a beneficiary and updates dialog state accordingly. + */ private fun handleBeneficiaryDeleteResult(action: BeneficiaryDeleteResultReceived) { when (action.result) { is DataState.Success -> { @@ -175,14 +189,25 @@ class AccountViewModel( } } +/** + * Represents the state of the account screen. + */ data class AccountState( val clientId: Long, val defaultAccountId: Long? = null, val dialogState: DialogState? = null, ) { + /** + * Defines various dialog UI states. + */ sealed interface DialogState { + /** Represents a loading dialog. */ data object Loading : DialogState + + /** Represents an error dialog with a message. */ data class Error(val message: String) : DialogState + + /** Dialog shown before deleting a beneficiary. */ data class DeleteBeneficiary( val title: StringResource, val message: StringResource, @@ -190,21 +215,26 @@ data class AccountState( ) : DialogState } + /** + * UI state representing the screen's visual content. + */ sealed interface ViewState { val hasFab: Boolean - val isPullToRefreshEnabled: Boolean + /** Loading view state. */ data object Loading : ViewState { override val hasFab: Boolean get() = false override val isPullToRefreshEnabled: Boolean get() = false } + /** Error view state with a message. */ data class Error(val message: String) : ViewState { override val hasFab: Boolean get() = false override val isPullToRefreshEnabled: Boolean get() = false } + /** Content view state with account and beneficiary info. */ data class Content( val accounts: List, val beneficiaries: List, @@ -215,26 +245,32 @@ data class AccountState( } } +/** + * Events that can be sent by [AccountViewModel] to update the UI. + */ sealed interface AccountEvent { data class OnAddEditSavingsAccount(val type: SavingsAddEditType) : AccountEvent data class OnNavigateToAccountDetail(val accountId: Long) : AccountEvent data class OnAddOrEditTPTBeneficiary(val type: BeneficiaryAddEditType) : AccountEvent - data class ShowToast(val message: String) : AccountEvent } +/** + * Actions that can be triggered from the UI to be handled by [AccountViewModel]. + */ sealed interface AccountAction { data object AddTPTBeneficiary : AccountAction data class EditBeneficiary(val beneficiary: Beneficiary) : AccountAction data class DeleteBeneficiary(val beneficiaryId: Long) : AccountAction - data object CreateSavingsAccount : AccountAction data class EditSavingsAccount(val accountId: Long) : AccountAction data class ViewAccountDetails(val accountId: Long) : AccountAction data class SetDefaultAccount(val accountId: Long, val accountNo: String) : AccountAction - data object DismissDialog : AccountAction + /** + * Internal-only actions used within the view model. + */ sealed interface Internal : AccountAction { data class DeleteBeneficiary(val beneficiaryId: Long) : Internal data class BeneficiaryDeleteResultReceived(val result: DataState) : Internal diff --git a/feature/auth/build.gradle.kts b/feature/auth/build.gradle.kts index 02ea83392..a1d9fb9c3 100644 --- a/feature/auth/build.gradle.kts +++ b/feature/auth/build.gradle.kts @@ -11,6 +11,7 @@ plugins { alias(libs.plugins.mifospay.cmp.feature) alias(libs.plugins.kotlin.parcelize) alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.dokka) } android { @@ -44,4 +45,23 @@ kotlin { implementation(libs.play.services.auth) } } +} + +tasks.withType().configureEach { + // Output format (HTML by default) + outputDirectory.set(layout.buildDirectory.dir("dokka")) + + // Module name in documentation + moduleName.set("feature") + + // Documentation format + dokkaSourceSets { + configureEach { + sourceLink { + localDirectory.set(file("src")) + remoteUrl.set(uri("https://github.com/openMF/mobile-wallet.git").toURL()) + remoteLineSuffix.set("#L") + } + } + } } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 41da833cc..04249cd3b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -23,7 +23,7 @@ org.gradle.configureondemand=false org.gradle.caching=true # Enable configuration caching between builds. -org.gradle.configuration-cache=true +org.gradle.configuration-cache=false # This option is set because of https://github.com/google/play-services-plugins/issues/246 # to generate the Configuration Cache regardless of incompatible tasks. # See https://github.com/android/nowinandroid/issues/1022 before using it. @@ -53,4 +53,5 @@ RblClientIdProp=a RblClientSecretProp=b kotlin.native.ignoreDisabledTargets=true -kotlin.mpp.androidGradlePluginCompatibility.nowarn=true \ No newline at end of file +kotlin.mpp.androidGradlePluginCompatibility.nowarn=true + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c14ff5498..d1611fd8f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -366,3 +366,4 @@ ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } spotless = { id = "com.diffplug.spotless", version.ref = "spotlessVersion" } version-catalog-linter = { id = "io.github.pemistahl.version-catalog-linter", version.ref = "versionCatalogLinterVersion" } +dokka = { id = "org.jetbrains.dokka", version = "2.0.0" } From 27518ad9a1afe1b1fb34be8fa1be57b3774022be Mon Sep 17 00:00:00 2001 From: rohitlokhande Date: Wed, 9 Apr 2025 19:59:17 +0530 Subject: [PATCH 2/7] feat: Add FeatureLibraryPlugin and update build configuration --- build-logic/convention/build.gradle.kts | 6 ++ .../src/main/kotlin/FeatureLibraryPlugin.kt | 19 ++++ feature/accounts/build.gradle.kts | 20 +--- feature/auth/build.gradle.kts | 20 +--- .../feature/auth/login/LoginViewModel.kt | 95 +++++++++++++++++++ gradle/libs.versions.toml | 4 + 6 files changed, 127 insertions(+), 37 deletions(-) create mode 100644 build-logic/convention/src/main/kotlin/FeatureLibraryPlugin.kt diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts index 2f8cf70e2..8f74dbbe6 100644 --- a/build-logic/convention/build.gradle.kts +++ b/build-logic/convention/build.gradle.kts @@ -2,6 +2,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { `kotlin-dsl` + alias(libs.plugins.dokka) } group = "org.mifospay.buildlogic" @@ -29,6 +30,7 @@ dependencies { compileOnly(libs.ktlint.gradlePlugin) compileOnly(libs.spotless.gradle) implementation(libs.truth) + implementation(libs.dokka.gradle.plugin) } tasks { @@ -91,5 +93,9 @@ gradlePlugin { implementationClass = "MifosGitHooksConventionPlugin" description = "Installs git hooks for the project" } + register("featureLibrary") { + id = "mifospay.feature.library" + implementationClass = "FeatureLibraryPlugin" + } } } diff --git a/build-logic/convention/src/main/kotlin/FeatureLibraryPlugin.kt b/build-logic/convention/src/main/kotlin/FeatureLibraryPlugin.kt new file mode 100644 index 000000000..2b3feb5bf --- /dev/null +++ b/build-logic/convention/src/main/kotlin/FeatureLibraryPlugin.kt @@ -0,0 +1,19 @@ +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.jetbrains.dokka.gradle.DokkaTask + +class FeatureLibraryPlugin : Plugin { + override fun apply(target: Project) { + target.tasks.withType(DokkaTask::class.java).configureEach { + outputDirectory.set(target.layout.buildDirectory.dir("dokka")) + moduleName.set("feature") + dokkaSourceSets.configureEach { + sourceLink { + localDirectory.set(target.file("src")) + remoteUrl.set(java.net.URI("https://github.com/openMF/mobile-wallet.git").toURL()) + remoteLineSuffix.set("#L") + } + } + } + } +} \ No newline at end of file diff --git a/feature/accounts/build.gradle.kts b/feature/accounts/build.gradle.kts index 62cc9f631..8128960cd 100644 --- a/feature/accounts/build.gradle.kts +++ b/feature/accounts/build.gradle.kts @@ -11,6 +11,7 @@ plugins { alias(libs.plugins.mifospay.cmp.feature) alias(libs.plugins.kotlin.parcelize) alias(libs.plugins.dokka) + alias(libs.plugins.mifospay.feature.library) } android { @@ -29,22 +30,3 @@ kotlin { } } } -// Configure Dokka -tasks.withType().configureEach { - // Output format (HTML by default) - outputDirectory.set(layout.buildDirectory.dir("dokka")) - - // Module name in documentation - moduleName.set("feature") - - // Documentation format - dokkaSourceSets { - configureEach { - sourceLink { - localDirectory.set(file("src")) - remoteUrl.set(uri("https://github.com/openMF/mobile-wallet.git").toURL()) - remoteLineSuffix.set("#L") - } - } - } -} \ No newline at end of file diff --git a/feature/auth/build.gradle.kts b/feature/auth/build.gradle.kts index a1d9fb9c3..1b912ce2d 100644 --- a/feature/auth/build.gradle.kts +++ b/feature/auth/build.gradle.kts @@ -12,6 +12,8 @@ plugins { alias(libs.plugins.kotlin.parcelize) alias(libs.plugins.kotlin.serialization) alias(libs.plugins.dokka) + alias(libs.plugins.mifospay.feature.library) + } android { @@ -47,21 +49,3 @@ kotlin { } } -tasks.withType().configureEach { - // Output format (HTML by default) - outputDirectory.set(layout.buildDirectory.dir("dokka")) - - // Module name in documentation - moduleName.set("feature") - - // Documentation format - dokkaSourceSets { - configureEach { - sourceLink { - localDirectory.set(file("src")) - remoteUrl.set(uri("https://github.com/openMF/mobile-wallet.git").toURL()) - remoteLineSuffix.set("#L") - } - } - } -} \ No newline at end of file diff --git a/feature/auth/src/commonMain/kotlin/org/mifospay/feature/auth/login/LoginViewModel.kt b/feature/auth/src/commonMain/kotlin/org/mifospay/feature/auth/login/LoginViewModel.kt index 5db33e8e6..610fb3b62 100644 --- a/feature/auth/src/commonMain/kotlin/org/mifospay/feature/auth/login/LoginViewModel.kt +++ b/feature/auth/src/commonMain/kotlin/org/mifospay/feature/auth/login/LoginViewModel.kt @@ -23,6 +23,12 @@ import org.mifospay.core.ui.utils.BaseViewModel private const val KEY_STATE = "state" +/** + * ViewModel responsible for managing login-related UI state and actions. + * + * @param loginUseCase Use case that performs the login operation. + * @param savedStateHandle Provides access to saved state. + */ class LoginViewModel( private val loginUseCase: LoginUseCase, savedStateHandle: SavedStateHandle, @@ -36,6 +42,11 @@ class LoginViewModel( } } + /** + * Handles incoming actions from the UI. + * + * @param action The [LoginAction] to process. + */ override fun handleAction(action: LoginAction) { when (action) { is LoginAction.UsernameChanged -> { @@ -74,6 +85,11 @@ class LoginViewModel( } } + /** + * Handles the result of the login attempt. + * + * @param action The internal login result action. + */ private fun handleLoginResult(action: LoginAction.Internal.ReceiveLoginResult) { when (action.loginResult) { is DataState.Error -> { @@ -99,6 +115,12 @@ class LoginViewModel( } } + /** + * Initiates the login process. + * + * @param username The user's username. + * @param password The user's password. + */ private fun loginUser( username: String, password: String, @@ -114,6 +136,14 @@ class LoginViewModel( } } +/** + * Represents the UI state of the login screen. + * + * @property username The entered username. + * @property password The entered password (excluded from parceling). + * @property isPasswordVisible Indicates whether the password is visible. + * @property dialogState Represents loading or error dialog state. + */ @Parcelize data class LoginState( val username: String = "", @@ -122,31 +152,96 @@ data class LoginState( val isPasswordVisible: Boolean = false, val dialogState: DialogState?, ) : Parcelable { + + /** + * Represents dialog states shown on the login screen. + */ sealed class DialogState : Parcelable { + /** + * Represents an error dialog. + * @param message The error message to display. + */ @Parcelize data class Error(val message: String) : DialogState() + /** + * Represents a loading dialog. + */ @Parcelize data object Loading : DialogState() } } +/** + * Defines one-time events to be triggered from the login screen. + */ sealed class LoginEvent { + /** + * Event to navigate back from the login screen. + */ data object NavigateBack : LoginEvent() + + /** + * Event to navigate to the signup screen. + */ data object NavigateToSignup : LoginEvent() + + /** + * Event to navigate to the passcode screen after successful login. + */ data object NavigateToPasscodeScreen : LoginEvent() + + /** + * Event to show a toast message. + * + * @param message The message to display. + */ data class ShowToast(val message: String) : LoginEvent() } +/** + * Represents all user interactions and internal actions on the login screen. + */ sealed class LoginAction { + /** + * Action for when the username is changed. + */ data class UsernameChanged(val username: String) : LoginAction() + + /** + * Action for when the password is changed. + */ data class PasswordChanged(val password: String) : LoginAction() + + /** + * Action to toggle password visibility. + */ data object TogglePasswordVisibility : LoginAction() + + /** + * Action to dismiss the error dialog. + */ data object ErrorDialogDismiss : LoginAction() + + /** + * Action for when the login button is clicked. + */ data object LoginClicked : LoginAction() + + /** + * Action for when the signup button is clicked. + */ data object SignupClicked : LoginAction() + /** + * Internal actions used within the ViewModel. + */ sealed class Internal : LoginAction() { + /** + * Result from the login use case. + * + * @param loginResult The result of the login attempt. + */ data class ReceiveLoginResult( val loginResult: DataState, ) : Internal() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d1611fd8f..2972a7f1d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,6 +24,7 @@ appcompatVersion = "1.7.0" coreKtxVersion = "1.15.0" # KotlinX Dependencies +dokkaGradlePlugin = "2.0.0" lifecycleExtensionsVersion = "2.2.0" lifecycleVersion = "2.8.7" @@ -178,6 +179,7 @@ androidx-profileinstaller = { group = "androidx.profileinstaller", name = "profi androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" } androidx-tracing-ktx = { group = "androidx.tracing", name = "tracing-ktx", version.ref = "androidxTracing" } +dokka-gradle-plugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokkaGradlePlugin" } ktlint-gradlePlugin = { group = "org.jlleitschuh.gradle", name = "ktlint-gradle", version.ref = "ktlint" } detekt-formatting = { group = "io.gitlab.arturbosch.detekt", name = "detekt-formatting", version.ref = "detekt" } spotless-gradle = { group = "com.diffplug.spotless", name = "spotless-plugin-gradle", version.ref = "spotlessVersion" } @@ -367,3 +369,5 @@ spotless = { id = "com.diffplug.spotless", version.ref = "spotlessVersion" } version-catalog-linter = { id = "io.github.pemistahl.version-catalog-linter", version.ref = "versionCatalogLinterVersion" } dokka = { id = "org.jetbrains.dokka", version = "2.0.0" } + +mifospay-feature-library = { id = "mifospay.feature.library", version = "unspecified" } \ No newline at end of file From d23b76b3752c0a11bf3e2365916f8fa5a534b81d Mon Sep 17 00:00:00 2001 From: rohitlokhande Date: Fri, 11 Apr 2025 00:09:46 +0530 Subject: [PATCH 3/7] feat: Integrate Dokka for documentation generation and update navigation popUpTo references --- .run/mifospay-android.run.xml | 7 +++++ .../main/kotlin/CMPFeatureConventionPlugin.kt | 2 ++ .../main/kotlin/KMPLibraryConventionPlugin.kt | 2 ++ .../src/main/kotlin/KMPLibraryPlugin.kt | 26 +++++++++++++++++++ build.gradle.kts | 23 +++++++++++++--- core/analytics/build.gradle.kts | 1 + .../kotlin/org/mifospay/shared/MifosPayApp.kt | 3 ++- .../shared/navigation/PasscodeNavGraph.kt | 2 +- .../org/mifospay/shared/ui/MifosAppState.kt | 2 +- 9 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 build-logic/convention/src/main/kotlin/KMPLibraryPlugin.kt diff --git a/.run/mifospay-android.run.xml b/.run/mifospay-android.run.xml index 0ec6fa917..6a085d20a 100644 --- a/.run/mifospay-android.run.xml +++ b/.run/mifospay-android.run.xml @@ -1,5 +1,7 @@ + +