diff --git a/androidApp/src/main/java/org/mifos/mobile/navigation/MifosNavGraph.kt b/androidApp/src/main/java/org/mifos/mobile/navigation/MifosNavGraph.kt index f6761ce22..127e1cbb5 100644 --- a/androidApp/src/main/java/org/mifos/mobile/navigation/MifosNavGraph.kt +++ b/androidApp/src/main/java/org/mifos/mobile/navigation/MifosNavGraph.kt @@ -132,7 +132,7 @@ fun RootNavGraph( ) notificationNavGraph( - navController = navController + navigateBack = navController::popBackStack ) locationsNavGraph( diff --git a/feature/notification/build.gradle.kts b/feature/notification/build.gradle.kts index 72e05e333..56422544d 100644 --- a/feature/notification/build.gradle.kts +++ b/feature/notification/build.gradle.kts @@ -1,3 +1,12 @@ +/* + * Copyright 2024 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-mobile/blob/master/LICENSE.md + */ plugins { alias(libs.plugins.mifos.android.feature) alias(libs.plugins.mifos.android.library.compose) @@ -8,16 +17,6 @@ android { } dependencies { - implementation(projects.core.ui) - implementation(projects.core.common) - implementation(projects.core.model) - implementation(projects.core.data) implementation(projects.core.datastore) - - // DBFlow implementation(libs.dbflow) - - testImplementation(libs.junit) - androidTestImplementation(libs.androidx.test.ext.junit) - androidTestImplementation(libs.espresso.core) } \ No newline at end of file diff --git a/feature/notification/src/androidTest/java/org/mifos/mobile/feature/notification/ExampleInstrumentedTest.kt b/feature/notification/src/androidTest/java/org/mifos/mobile/feature/notification/ExampleInstrumentedTest.kt deleted file mode 100644 index 35fb3c53b..000000000 --- a/feature/notification/src/androidTest/java/org/mifos/mobile/feature/notification/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package org.mifos.mobile.feature.notification - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("org.mifos.mobile.feature.notification.test", appContext.packageName) - } -} \ No newline at end of file diff --git a/feature/notification/src/main/AndroidManifest.xml b/feature/notification/src/main/AndroidManifest.xml index a5918e68a..1f9b243f0 100644 --- a/feature/notification/src/main/AndroidManifest.xml +++ b/feature/notification/src/main/AndroidManifest.xml @@ -1,4 +1,13 @@ + \ No newline at end of file diff --git a/feature/notification/src/main/java/org/mifos/mobile/feature/notification/NotificationScreen.kt b/feature/notification/src/main/java/org/mifos/mobile/feature/notification/NotificationScreen.kt index 354f15f41..ab39a3cb8 100644 --- a/feature/notification/src/main/java/org/mifos/mobile/feature/notification/NotificationScreen.kt +++ b/feature/notification/src/main/java/org/mifos/mobile/feature/notification/NotificationScreen.kt @@ -1,3 +1,12 @@ +/* + * Copyright 2024 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-mobile/blob/master/LICENSE.md + */ package org.mifos.mobile.feature.notification import androidx.compose.foundation.layout.Box @@ -15,7 +24,6 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.material3.pulltorefresh.PullToRefreshContainer import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.runtime.Composable @@ -30,7 +38,6 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource 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 @@ -40,15 +47,18 @@ import org.mifos.mobile.core.common.Network import org.mifos.mobile.core.common.utils.DateHelper import org.mifos.mobile.core.datastore.model.MifosNotification import org.mifos.mobile.core.designsystem.components.MifosScaffold +import org.mifos.mobile.core.designsystem.components.MifosTextButton import org.mifos.mobile.core.designsystem.theme.MifosMobileTheme import org.mifos.mobile.core.ui.component.EmptyDataView import org.mifos.mobile.core.ui.component.MifosErrorComponent import org.mifos.mobile.core.ui.component.MifosProgressIndicatorOverlay +import org.mifos.mobile.core.ui.utils.DevicePreviews @Composable -fun NotificationScreen( - viewModel: NotificationViewModel = hiltViewModel(), +internal fun NotificationScreen( navigateBack: () -> Unit, + modifier: Modifier = Modifier, + viewModel: NotificationViewModel = hiltViewModel(), ) { val uiState by viewModel.notificationUiState.collectAsStateWithLifecycle() val isRefreshing by viewModel.isRefreshing.collectAsStateWithLifecycle() @@ -56,39 +66,41 @@ fun NotificationScreen( NotificationScreen( uiState = uiState, navigateBack = navigateBack, - onRetry = { viewModel.loadNotifications() }, - dismissNotification = { viewModel.dismissNotification(it) }, - onRefresh = { viewModel.refreshNotifications() }, - isRefreshing = isRefreshing + onRetry = viewModel::loadNotifications, + dismissNotification = viewModel::dismissNotification, + onRefresh = viewModel::refreshNotifications, + isRefreshing = isRefreshing, + modifier = modifier, ) } @Composable -fun NotificationScreen( +private fun NotificationScreen( uiState: NotificationUiState, navigateBack: () -> Unit, onRetry: () -> Unit, dismissNotification: (MifosNotification) -> Unit, isRefreshing: Boolean, - onRefresh: () -> Unit + onRefresh: () -> Unit, + modifier: Modifier = Modifier, ) { val context = LocalContext.current + MifosScaffold( topBarTitleResId = R.string.notification, navigateBack = navigateBack, + modifier = modifier, content = { Box(modifier = Modifier.padding(it)) { when (uiState) { - is NotificationUiState.Loading -> { - MifosProgressIndicatorOverlay() - } + is NotificationUiState.Loading -> MifosProgressIndicatorOverlay() is NotificationUiState.Error -> { MifosErrorComponent( message = uiState.errorMessage, isNetworkConnected = Network.isConnected(context), isRetryEnabled = true, - onRetry = onRetry + onRetry = onRetry, ) } @@ -97,30 +109,31 @@ fun NotificationScreen( EmptyDataView( icon = R.drawable.ic_notifications, error = R.string.no_notification, - modifier = Modifier.fillMaxSize() + modifier = Modifier.fillMaxSize(), ) } else { NotificationContent( + isRefreshing = isRefreshing, notifications = uiState.notifications, dismissNotification = dismissNotification, onRefresh = onRefresh, - isRefreshing = isRefreshing ) } } } } - } + }, ) } @OptIn(ExperimentalMaterial3Api::class) @Composable -fun NotificationContent( +private fun NotificationContent( + isRefreshing: Boolean, notifications: List, dismissNotification: (MifosNotification) -> Unit, - isRefreshing: Boolean, - onRefresh: () -> Unit + onRefresh: () -> Unit, + modifier: Modifier = Modifier, ) { val pullRefreshState = rememberPullToRefreshState() @@ -131,20 +144,25 @@ fun NotificationContent( } LaunchedEffect(key1 = isRefreshing) { - if (isRefreshing) + if (isRefreshing) { pullRefreshState.startRefresh() - else + } else { pullRefreshState.endRefresh() + } } - Box(modifier = Modifier.fillMaxSize().nestedScroll(pullRefreshState.nestedScrollConnection)) { + Box( + modifier = modifier + .fillMaxSize() + .nestedScroll(pullRefreshState.nestedScrollConnection), + ) { LazyColumn(modifier = Modifier.fillMaxSize()) { itemsIndexed(items = notifications) { index, notification -> NotificationItem( notification = notification, - dismissNotification = dismissNotification + dismissNotification = dismissNotification, ) - if(index < notifications.lastIndex) { + if (index < notifications.lastIndex) { HorizontalDivider(color = MaterialTheme.colorScheme.surfaceDim) } } @@ -157,63 +175,72 @@ fun NotificationContent( } @Composable -fun NotificationItem( +private fun NotificationItem( notification: MifosNotification, - dismissNotification: (MifosNotification) -> Unit + dismissNotification: (MifosNotification) -> Unit, + modifier: Modifier = Modifier, ) { - var isRead = rememberSaveable { mutableStateOf(notification.isRead()) } + val isRead = rememberSaveable { mutableStateOf(notification.isRead()) } + Row( verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(8.dp) + modifier = modifier.padding(8.dp), ) { Icon( painter = painterResource(id = R.drawable.ic_notifications), contentDescription = null, - tint = if(isRead.value) MaterialTheme.colorScheme.onSurfaceVariant - else MaterialTheme.colorScheme.primary + tint = if (isRead.value) { + MaterialTheme.colorScheme.onSurfaceVariant + } else { + MaterialTheme.colorScheme.primary + }, ) Spacer(modifier = Modifier.width(8.dp)) Column( - horizontalAlignment = Alignment.End + horizontalAlignment = Alignment.End, ) { Text( modifier = Modifier.fillMaxWidth(), text = notification.msg ?: "", - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyMedium, ) Text( modifier = Modifier.alpha(0.7f), text = DateHelper.getDateAndTimeAsStringFromLong(notification.timeStamp), style = MaterialTheme.typography.labelMedium, ) - if(!isRead.value) { - TextButton( + if (!isRead.value) { + MifosTextButton( + text = stringResource(id = R.string.dialog_action_ok), onClick = { isRead.value = true dismissNotification(notification) - } - ) { - Text(text = stringResource(id = R.string.dialog_action_ok)) - } + }, + ) } } } } -class NotificationUiStatePreviews : PreviewParameterProvider { +internal class NotificationUiStatePreviews : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - NotificationUiState.Success(notifications = listOf(MifosNotification(), MifosNotification())), + NotificationUiState.Success( + notifications = listOf( + MifosNotification(), + MifosNotification(), + ), + ), NotificationUiState.Error(errorMessage = ""), NotificationUiState.Loading, ) } - -@Preview(showSystemUi = true) +@DevicePreviews @Composable -fun NotificationScreenPreview( - @PreviewParameter(NotificationUiStatePreviews::class) notificationUiState: NotificationUiState +private fun NotificationScreenPreview( + @PreviewParameter(NotificationUiStatePreviews::class) + notificationUiState: NotificationUiState, ) { MifosMobileTheme { NotificationScreen( @@ -222,7 +249,7 @@ fun NotificationScreenPreview( onRetry = {}, dismissNotification = {}, onRefresh = {}, - isRefreshing = false + isRefreshing = false, ) } -} \ No newline at end of file +} diff --git a/feature/notification/src/main/java/org/mifos/mobile/feature/notification/NotificationViewModel.kt b/feature/notification/src/main/java/org/mifos/mobile/feature/notification/NotificationViewModel.kt index 039e1c065..a639b6f3e 100644 --- a/feature/notification/src/main/java/org/mifos/mobile/feature/notification/NotificationViewModel.kt +++ b/feature/notification/src/main/java/org/mifos/mobile/feature/notification/NotificationViewModel.kt @@ -1,3 +1,12 @@ +/* + * Copyright 2024 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-mobile/blob/master/LICENSE.md + */ package org.mifos.mobile.feature.notification import androidx.lifecycle.ViewModel @@ -9,16 +18,18 @@ import kotlinx.coroutines.flow.catch import kotlinx.coroutines.launch import org.mifos.mobile.core.data.repository.NotificationRepository import org.mifos.mobile.core.datastore.model.MifosNotification +import org.mifos.mobile.feature.notification.NotificationUiState.Loading import javax.inject.Inject @HiltViewModel -class NotificationViewModel @Inject constructor(private val notificationRepositoryImp: NotificationRepository) : - ViewModel() { +internal class NotificationViewModel @Inject constructor( + private val notificationRepositoryImp: NotificationRepository, +) : ViewModel() { - private val _notificationUiState = MutableStateFlow(NotificationUiState.Loading) + private val _notificationUiState = MutableStateFlow(Loading) val notificationUiState: StateFlow get() = _notificationUiState - private val _isRefreshing = MutableStateFlow(false) + private val _isRefreshing = MutableStateFlow(false) val isRefreshing: StateFlow get() = _isRefreshing init { @@ -26,14 +37,16 @@ class NotificationViewModel @Inject constructor(private val notificationReposito } fun loadNotifications() { - _notificationUiState.value = NotificationUiState.Loading + _notificationUiState.value = Loading viewModelScope.launch { notificationRepositoryImp.loadNotifications() .catch { - _notificationUiState.value = NotificationUiState.Error(errorMessage = it.message) + _notificationUiState.value = + NotificationUiState.Error(errorMessage = it.message) }.collect { notifications -> _isRefreshing.emit(false) - _notificationUiState.value = NotificationUiState.Success(notifications = notifications) + _notificationUiState.value = + NotificationUiState.Success(notifications = notifications) } } } @@ -49,9 +62,8 @@ class NotificationViewModel @Inject constructor(private val notificationReposito } } - -sealed class NotificationUiState { +internal sealed class NotificationUiState { data object Loading : NotificationUiState() data class Success(val notifications: List) : NotificationUiState() data class Error(val errorMessage: String?) : NotificationUiState() -} \ No newline at end of file +} diff --git a/feature/notification/src/main/java/org/mifos/mobile/feature/notification/navigation/NotificationNavGraph.kt b/feature/notification/src/main/java/org/mifos/mobile/feature/notification/navigation/NotificationNavGraph.kt index 038105d33..31eaed005 100644 --- a/feature/notification/src/main/java/org/mifos/mobile/feature/notification/navigation/NotificationNavGraph.kt +++ b/feature/notification/src/main/java/org/mifos/mobile/feature/notification/navigation/NotificationNavGraph.kt @@ -1,3 +1,12 @@ +/* + * Copyright 2024 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-mobile/blob/master/LICENSE.md + */ package org.mifos.mobile.feature.notification.navigation import androidx.navigation.NavController @@ -11,14 +20,14 @@ fun NavController.navigateToNotificationScreen() { } fun NavGraphBuilder.notificationNavGraph( - navController: NavController + navigateBack: () -> Unit, ) { navigation( startDestination = NotificationNavigation.NotificationScreen.route, route = NotificationNavigation.NotificationBase.route, ) { notificationScreenRoute( - navigateBack = navController::popBackStack, + navigateBack = navigateBack, ) } } @@ -33,4 +42,4 @@ fun NavGraphBuilder.notificationScreenRoute( navigateBack = navigateBack, ) } -} \ No newline at end of file +} diff --git a/feature/notification/src/main/java/org/mifos/mobile/feature/notification/navigation/NotificationNavigation.kt b/feature/notification/src/main/java/org/mifos/mobile/feature/notification/navigation/NotificationNavigation.kt index c6070917b..d37ba440d 100644 --- a/feature/notification/src/main/java/org/mifos/mobile/feature/notification/navigation/NotificationNavigation.kt +++ b/feature/notification/src/main/java/org/mifos/mobile/feature/notification/navigation/NotificationNavigation.kt @@ -1,3 +1,12 @@ +/* + * Copyright 2024 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-mobile/blob/master/LICENSE.md + */ package org.mifos.mobile.feature.notification.navigation const val NOTIFICATION_NAVIGATION_ROUTE_BASE = "notification_base_route" diff --git a/feature/notification/src/main/res/drawable/ic_notifications.xml b/feature/notification/src/main/res/drawable/ic_notifications.xml index 94190607d..5f3a73aef 100644 --- a/feature/notification/src/main/res/drawable/ic_notifications.xml +++ b/feature/notification/src/main/res/drawable/ic_notifications.xml @@ -1,3 +1,13 @@ + + + Notification OK diff --git a/feature/notification/src/test/java/org/mifos/mobile/feature/notification/ExampleUnitTest.kt b/feature/notification/src/test/java/org/mifos/mobile/feature/notification/ExampleUnitTest.kt deleted file mode 100644 index cf9b3e8f3..000000000 --- a/feature/notification/src/test/java/org/mifos/mobile/feature/notification/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package org.mifos.mobile.feature.notification - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} \ No newline at end of file