Skip to content

feat : Activate migrated to CMP #2388

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
May 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmp-navigation/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ kotlin {
implementation(projects.core.network)

implementation(projects.feature.about)
// implementation(projects.feature.activate)
implementation(projects.feature.activate)
implementation(projects.feature.auth)
// implementation(projects.feature.center)
// implementation(projects.feature.checkerInboxTask)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.mifos.core.datastore.di.PreferencesModule
import com.mifos.core.domain.di.UseCaseModule
import com.mifos.core.network.di.DataManagerModule
import com.mifos.core.network.di.NetworkModule
import com.mifos.feature.activate.di.ActivateModule
import com.mifos.feature.auth.di.AuthModule
import com.mifos.feature.note.di.NoteModule
import com.mifos.room.di.DaoModule
Expand Down Expand Up @@ -46,7 +47,7 @@ object KoinModules {
private val featureModules = module {
includes(
// AboutModule,
// ActivateModule,
ActivateModule,
AuthModule,
// CenterModule,
// CheckerInboxTaskModule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
Expand All @@ -23,6 +24,8 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import cmp.navigation.AppState
import com.mifos.feature.about.navigation.aboutNavGraph
import com.mifos.feature.activate.navigation.activateScreen
import com.mifos.feature.activate.navigation.navigateToActivateScreen
import com.mifos.feature.note.navigation.noteNavGraph

const val WELCOME_ROUTE = "home_screen"
Expand All @@ -39,27 +42,32 @@ internal fun FeatureNavHost(
navController = appState.navController,
modifier = modifier,
) {
homeScreen()
homeScreen(onClick = { appState.navController.navigateToActivateScreen(0, "") })

aboutNavGraph(onBackPressed = appState.navController::popBackStack)

noteNavGraph(onBackPressed = appState.navController::popBackStack)

activateScreen(onBackPressed = appState.navController::popBackStack)
}
}

fun NavGraphBuilder.homeScreen() {
fun NavGraphBuilder.homeScreen(onClick: () -> Unit) {
composable(route = HomeDestinationsScreen.SearchScreen.route) {
WelcomeScreen()
WelcomeScreen(onClick)
}
}

@Composable
fun WelcomeScreen() {
fun WelcomeScreen(onClick: () -> Unit) {
Column(
modifier = Modifier.fillMaxSize().background(Color.White),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(text = "Welcome to Mifos", color = Color.Black)
Button(onClick = onClick) {
Text("navigate")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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/android-client/blob/master/LICENSE.md
*/
package com.mifos.core.common.utils

import kotlinx.datetime.Instant
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime

fun formatDate(millis: Long): String {
val dateTime = Instant.fromEpochMilliseconds(millis).toLocalDateTime(TimeZone.currentSystemDefault())
return "${dateTime.dayOfMonth} ${dateTime.month.name.lowercase().replaceFirstChar { it.uppercase() }} ${dateTime.year}"
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import com.mifos.core.ui.util.DevicePreview
fun MifosAlertDialog(
dialogTitle: String,
dialogText: String,
dismissText: String,
confirmationText: String,
dismissText: String = "Cancel",
confirmationText: String = "Ok",
onDismissRequest: () -> Unit,
onConfirmation: () -> Unit,
modifier: Modifier = Modifier,
Expand Down
19 changes: 10 additions & 9 deletions feature/activate/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,20 @@
* See https://github.com/openMF/android-client/blob/master/LICENSE.md
*/
plugins {
alias(libs.plugins.mifos.android.feature)
alias(libs.plugins.mifos.android.library.compose)
alias(libs.plugins.mifos.android.library.jacoco)
alias(libs.plugins.mifos.cmp.feature)
}

android {
namespace = "com.mifos.feature.activate"
}

dependencies {

implementation(projects.core.domain)

testImplementation(libs.hilt.android.testing)

kotlin {
sourceSets {
commonMain.dependencies {
implementation(compose.material3)
implementation(compose.components.resources)
implementation(compose.ui)
implementation(projects.core.domain)
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@

package com.mifos.feature.activate

import android.widget.Toast
import androidclient.feature.activate.generated.resources.Res
import androidclient.feature.activate.generated.resources.feature_activate
import androidclient.feature.activate.generated.resources.feature_activate_activation_date
import androidclient.feature.activate.generated.resources.feature_activate_cancel
import androidclient.feature.activate.generated.resources.feature_activate_client
import androidclient.feature.activate.generated.resources.feature_activate_failed_to_activate_client
import androidclient.feature.activate.generated.resources.feature_activate_select
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
Expand All @@ -23,36 +29,38 @@ import androidx.compose.material3.Button
import androidx.compose.material3.DatePicker
import androidx.compose.material3.DatePickerDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SelectableDates
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.rememberDatePickerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.mifos.core.common.utils.Constants
import com.mifos.core.common.utils.formatDate
import com.mifos.core.designsystem.component.MifosButton
import com.mifos.core.designsystem.component.MifosCircularProgress
import com.mifos.core.designsystem.component.MifosDatePickerTextField
import com.mifos.core.designsystem.component.MifosScaffold
import com.mifos.core.designsystem.component.MifosSweetError
import com.mifos.core.model.objects.clients.ActivatePayload
import org.koin.androidx.compose.koinViewModel
import java.text.SimpleDateFormat
import java.util.Locale
import com.mifos.core.ui.components.MifosAlertDialog
import com.mifos.core.ui.util.DevicePreview
import kotlinx.datetime.Clock
import org.jetbrains.compose.resources.getString
import org.jetbrains.compose.resources.stringResource
import org.koin.compose.viewmodel.koinViewModel

@Composable
internal fun ActivateScreen(
Expand All @@ -63,6 +71,7 @@ internal fun ActivateScreen(
val id by viewModel.id.collectAsStateWithLifecycle()
val activateType by viewModel.activateType.collectAsStateWithLifecycle()


ActivateScreen(
state = state,
onActivate = {
Expand Down Expand Up @@ -101,25 +110,22 @@ internal fun ActivateScreen(
onBackPressed: () -> Unit,
modifier: Modifier = Modifier,
) {
val snackbarHostState = remember { SnackbarHostState() }

MifosScaffold(
title = stringResource(id = R.string.feature_activate),
title = stringResource(Res.string.feature_activate),
onBackPressed = onBackPressed,
snackbarHostState = snackbarHostState,
) { paddingValues ->
Column(modifier = modifier.padding(paddingValues)) {
when (state) {
is ActivateUiState.ActivatedSuccessfully -> {
Toast.makeText(
LocalContext.current,
stringResource(id = state.message),
Toast.LENGTH_SHORT,
).show()
onBackPressed()
MifosAlertDialog(
dialogTitle = "Success",
dialogText = stringResource(state.message),
onConfirmation = onBackPressed,
onDismissRequest = onBackPressed,
)
}

is ActivateUiState.Error -> MifosSweetError(message = stringResource(id = state.message)) {}
is ActivateUiState.Error -> MifosSweetError(message = stringResource(state.message)) {}

is ActivateUiState.Loading -> MifosCircularProgress()

Expand All @@ -136,12 +142,12 @@ private fun ActivateContent(
) {
Column(modifier = modifier) {
var showDatePicker by rememberSaveable { mutableStateOf(false) }
var activateDate by rememberSaveable { mutableLongStateOf(System.currentTimeMillis()) }
var activateDate by rememberSaveable { mutableLongStateOf(Clock.System.now().toEpochMilliseconds()) }
val datePickerState = rememberDatePickerState(
initialSelectedDateMillis = activateDate,
selectableDates = object : SelectableDates {
override fun isSelectableDate(utcTimeMillis: Long): Boolean {
return utcTimeMillis >= System.currentTimeMillis()
return utcTimeMillis >= Clock.System.now().toEpochMilliseconds()
}
},
)
Expand All @@ -159,33 +165,31 @@ private fun ActivateContent(
activateDate = it
}
},
) { Text(stringResource(id = R.string.feature_activate_select)) }
) { Text(stringResource(Res.string.feature_activate_select)) }
},
dismissButton = {
TextButton(
onClick = {
showDatePicker = false
},
) { Text(stringResource(id = R.string.feature_activate_cancel)) }
) { Text(stringResource(Res.string.feature_activate_cancel)) }
},
) {
DatePicker(state = datePickerState)
}
}

MifosDatePickerTextField(
value = SimpleDateFormat("dd MMMM yyyy", Locale.getDefault()).format(
activateDate,
),
label = stringResource(R.string.feature_activate_activation_date),
value = formatDate(activateDate),
label = stringResource(Res.string.feature_activate_activation_date),
openDatePicker = {
showDatePicker = true
},
)

Spacer(modifier = Modifier.height(16.dp))

Button(
MifosButton(
onClick = {
onActivate(
ActivatePayload(
Expand All @@ -199,30 +203,48 @@ private fun ActivateContent(
.padding(start = 16.dp, end = 16.dp),
contentPadding = PaddingValues(),
) {
Text(text = stringResource(id = R.string.feature_activate), fontSize = 16.sp)
Text(text = stringResource(Res.string.feature_activate),
style= MaterialTheme.typography.bodySmall)
}
}
}

private class ActivateUiStateProvider : PreviewParameterProvider<ActivateUiState> {
@DevicePreview
@Composable
private fun ActivateScreenPreviewInitial() {
ActivateScreen(
state = ActivateUiState.Initial,
onActivate = {},
onBackPressed = {},
)
}

override val values: Sequence<ActivateUiState>
get() = sequenceOf(
ActivateUiState.Loading,
ActivateUiState.Error(R.string.feature_activate_failed_to_activate_client),
ActivateUiState.ActivatedSuccessfully(R.string.feature_activate_client),
ActivateUiState.Initial,
)
@DevicePreview
@Composable
private fun ActivateScreenPreviewLoading() {
ActivateScreen(
state = ActivateUiState.Loading,
onActivate = {},
onBackPressed = {},
)
}

@Preview(showBackground = true)
@DevicePreview
@Composable
private fun ActivateScreenPreview(
@PreviewParameter(ActivateUiStateProvider::class) state: ActivateUiState,
) {
private fun ActivateScreenPreviewActivatedSuccessfully() {
ActivateScreen(
state = state,
state = ActivateUiState.ActivatedSuccessfully(Res.string.feature_activate_client),
onActivate = {},
onBackPressed = {},
)
}

@DevicePreview
@Composable
private fun ActivateScreenPreviewError() {
ActivateScreen(
state = ActivateUiState.Error(Res.string.feature_activate_failed_to_activate_client),
onActivate = {},
onBackPressed = {},
)
}
Loading