From 856179cafa2057a2d229e2eec7ecea6af5dd059c Mon Sep 17 00:00:00 2001 From: Pablo Pajuelo Cabezas Date: Fri, 20 Dec 2024 15:13:00 +0100 Subject: [PATCH] feat: [ANDROAPP-6689] Create DataSetInstanceScreen --- aggregates/.gitignore | 1 + aggregates/build.gradle.kts | 20 +- .../ui/DataSetTableScreenPreview.kt | 22 ++ .../dhis2/mobile/aggregates/ui/ScreenWidth.kt | 9 + .../mobile/aggregates/model/DataSetDetails.kt | 8 + .../aggregates/model/DataSetScreenState.kt | 27 ++ .../mobile/aggregates/model/DataSetSection.kt | 6 + .../mobile/aggregates/ui/AdaptativeTabRow.kt | 130 +++++++ .../mobile/aggregates/ui/DataSetDetailRow.kt | 96 +++++ .../aggregates/ui/DataSetTableScreen.kt | 338 +++++++++++++++++- .../dhis2/mobile/aggregates/ui/ScreenWidth.kt | 7 + .../mobile/aggregates/ui/TwoPaneScreen.kt | 40 +++ .../aggregates/ui/ScreenWidth.desktop.kt | 13 + .../dhis2/mobile/aggregates/ui/ScreenWidth.kt | 13 + app/src/main/AndroidManifest.xml | 1 + .../dataSetTable/DataSetInstanceActivity.kt | 34 ++ .../datasetList/DataSetListFragment.kt | 13 +- .../general/SessionManagerActivity.kt | 2 +- .../dhis2/usescases/login/ui/LoginScreen.kt | 7 +- .../dhis2/usescases/qrScanner/ScanActivity.kt | 2 +- .../searchTrackEntity/ui/SearchTEUi.kt | 4 +- .../utils/granularsync/SMSSenderHelper.kt | 2 +- build.gradle.kts | 5 +- .../dhis2/commons/ActivityResultObserver.kt | 2 +- .../data/FeatureConfigRepositoryImpl.kt | 1 + .../commons/featureconfig/model/Feature.kt | 1 + .../charts/ui/IndicatorViewHolder.kt | 6 +- gradle/libs.versions.toml | 14 +- .../android/rtsm/ui/base/BaseActivity.kt | 2 +- 29 files changed, 795 insertions(+), 31 deletions(-) create mode 100644 aggregates/.gitignore create mode 100644 aggregates/src/androidMain/kotlin/org/dhis2/mobile/aggregates/ui/DataSetTableScreenPreview.kt create mode 100644 aggregates/src/androidMain/kotlin/org/dhis2/mobile/aggregates/ui/ScreenWidth.kt create mode 100644 aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/model/DataSetDetails.kt create mode 100644 aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/model/DataSetScreenState.kt create mode 100644 aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/model/DataSetSection.kt create mode 100644 aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/ui/AdaptativeTabRow.kt create mode 100644 aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/ui/DataSetDetailRow.kt create mode 100644 aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/ui/ScreenWidth.kt create mode 100644 aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/ui/TwoPaneScreen.kt create mode 100644 aggregates/src/desktopMain/kotlin/org/dhis2/mobile/aggregates/ui/ScreenWidth.desktop.kt create mode 100644 aggregates/src/iosMain/kotlin/org/dhis2/mobile/aggregates/ui/ScreenWidth.kt create mode 100644 app/src/main/java/org/dhis2/usescases/datasets/dataSetTable/DataSetInstanceActivity.kt diff --git a/aggregates/.gitignore b/aggregates/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/aggregates/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/aggregates/build.gradle.kts b/aggregates/build.gradle.kts index 2b1ab86e06..be6dc15056 100644 --- a/aggregates/build.gradle.kts +++ b/aggregates/build.gradle.kts @@ -5,6 +5,12 @@ plugins { alias(libs.plugins.kotlin.compose.compiler) } +repositories{ + maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") } + mavenCentral() + google() +} + kotlin { androidTarget { compilations.all { @@ -14,7 +20,8 @@ kotlin { } } - jvm("desktop") + jvm("desktop"). + /*listOf( iosX64(), @@ -34,15 +41,16 @@ kotlin { implementation(compose.ui) implementation(compose.material3) api(compose.materialIconsExtended) - @OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class) - implementation(compose.components.resources) implementation(libs.dhis2.mobile.designsystem) + implementation(libs.compose.material3.window) } commonTest.dependencies { implementation(kotlin("test")) } - androidMain.dependencies { } + androidMain.dependencies { + implementation(libs.androidx.compose.preview) + } androidUnitTest.dependencies { } @@ -65,3 +73,7 @@ android { targetCompatibility = JavaVersion.VERSION_1_8 } } +dependencies { + debugImplementation(libs.androidx.compose.preview) + debugImplementation(libs.androidx.ui.tooling) +} \ No newline at end of file diff --git a/aggregates/src/androidMain/kotlin/org/dhis2/mobile/aggregates/ui/DataSetTableScreenPreview.kt b/aggregates/src/androidMain/kotlin/org/dhis2/mobile/aggregates/ui/DataSetTableScreenPreview.kt new file mode 100644 index 0000000000..bb401f9c3c --- /dev/null +++ b/aggregates/src/androidMain/kotlin/org/dhis2/mobile/aggregates/ui/DataSetTableScreenPreview.kt @@ -0,0 +1,22 @@ +package org.dhis2.mobile.aggregates.ui + +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview +import org.dhis2.mobile.aggregates.model.previewDataSetScreenState +import org.hisp.dhis.mobile.ui.designsystem.theme.DHIS2Theme + +@Preview(device = "id:pixel_8a") +@Composable +fun DataSetTableScreenPreview() { + DHIS2Theme { + DataSetInstanceScreen(previewDataSetScreenState(false, 3)) {} + } +} + +@Preview(device = "id:pixel_c") +@Composable +fun DataSetTableTabletScreenPreview() { + DHIS2Theme { + DataSetInstanceScreen(previewDataSetScreenState(true, 10)) {} + } +} diff --git a/aggregates/src/androidMain/kotlin/org/dhis2/mobile/aggregates/ui/ScreenWidth.kt b/aggregates/src/androidMain/kotlin/org/dhis2/mobile/aggregates/ui/ScreenWidth.kt new file mode 100644 index 0000000000..9ab190424c --- /dev/null +++ b/aggregates/src/androidMain/kotlin/org/dhis2/mobile/aggregates/ui/ScreenWidth.kt @@ -0,0 +1,9 @@ +package org.dhis2.mobile.aggregates.ui + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +@Composable +actual fun getScreenWidth(): Dp = LocalConfiguration.current.screenWidthDp.dp diff --git a/aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/model/DataSetDetails.kt b/aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/model/DataSetDetails.kt new file mode 100644 index 0000000000..f2a6ab313d --- /dev/null +++ b/aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/model/DataSetDetails.kt @@ -0,0 +1,8 @@ +package org.dhis2.mobile.aggregates.model + +data class DataSetDetails( + val titleLabel: String, + val dateLabel: String, + val orgUnitLabel: String, + val catOptionComboLabel: String, +) diff --git a/aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/model/DataSetScreenState.kt b/aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/model/DataSetScreenState.kt new file mode 100644 index 0000000000..80feee1fe1 --- /dev/null +++ b/aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/model/DataSetScreenState.kt @@ -0,0 +1,27 @@ +package org.dhis2.mobile.aggregates.model + +data class DataSetScreenState( + val dataSetDetails: DataSetDetails, + val dataSetSections: List, + val useTwoPane: Boolean, +) + +inline fun previewDataSetScreenState( + useTwoPane: Boolean, + numberOfTabs: Int, +) = DataSetScreenState( + dataSetDetails = DataSetDetails( + titleLabel = "Data set title", + dateLabel = "Jan. 2024", + orgUnitLabel = "Org. Unit", + catOptionComboLabel = "Cat. Option Combo", + ), + dataSetSections = buildList { + repeat(numberOfTabs) { + add( + DataSetSection("uid$it", "Section $it"), + ) + } + }, + useTwoPane = useTwoPane, +) diff --git a/aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/model/DataSetSection.kt b/aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/model/DataSetSection.kt new file mode 100644 index 0000000000..bbb14d7e4f --- /dev/null +++ b/aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/model/DataSetSection.kt @@ -0,0 +1,6 @@ +package org.dhis2.mobile.aggregates.model + +data class DataSetSection( + val uid: String, + val title: String, +) diff --git a/aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/ui/AdaptativeTabRow.kt b/aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/ui/AdaptativeTabRow.kt new file mode 100644 index 0000000000..3cf33496fe --- /dev/null +++ b/aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/ui/AdaptativeTabRow.kt @@ -0,0 +1,130 @@ +package org.dhis2.mobile.aggregates.ui + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ScrollableTabRow +import androidx.compose.material3.Tab +import androidx.compose.material3.TabRow +import androidx.compose.material3.TabRowDefaults +import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.dp +import org.hisp.dhis.mobile.ui.designsystem.theme.Spacing + +/** + * Adaptive Tab Row + * Displays a scrollable tab row if the total width of the tabs exceeds the screen width. + * @param modifier Modifier for styling + * @param tabLabels List of tab labels to display + * @param onTabClicked Callback function to be invoked when a tab is clicked + * **/ +@Composable +fun AdaptiveTabRow( + modifier: Modifier = Modifier, + tabLabels: List, + onTabClicked: (index: Int) -> Unit, +) { + var selectedTab by remember { mutableStateOf(0) } + val tabWidths = remember { mutableStateListOf() } + var scrollable by remember { mutableStateOf(false) } + + // Calculate total width of tabs + val totalTabWidth = tabWidths.sum() + val screenWidth = with(LocalDensity.current) { + getScreenWidth().roundToPx() + } + + LaunchedEffect(key1 = totalTabWidth, key2 = screenWidth) { + // Determine if tabs should be scrollable + scrollable = totalTabWidth > screenWidth + } + + // TabRow with conditional behavior + if (false) { + ScrollableTabRow( + modifier = modifier + .height(48.dp) + .fillMaxWidth(), + selectedTabIndex = selectedTab, + containerColor = MaterialTheme.colorScheme.primary, + edgePadding = Spacing.Spacing16, + indicator = { tabPositions -> + TabRowDefaults.PrimaryIndicator( + width = 56.dp, + modifier = Modifier.tabIndicatorOffset(tabPositions[selectedTab]), + color = MaterialTheme.colorScheme.onPrimary, + ) + }, + divider = {}, + ) { + tabLabels.forEachIndexed { index, tabLabel -> + Tab( + modifier = Modifier + .height(48.dp) + .onGloballyPositioned { coordinates -> + tabWidths.add(index, coordinates.size.width) + }, + selected = selectedTab == index, + onClick = { + selectedTab = index + onTabClicked(index) + }, + ) { + Text( + text = tabLabel, + color = MaterialTheme.colorScheme.onPrimary, + style = MaterialTheme.typography.titleSmall, + ) + } + } + } + } else { + TabRow( + modifier = modifier + .height(48.dp) + .fillMaxWidth(), + selectedTabIndex = selectedTab, + containerColor = MaterialTheme.colorScheme.primary, + indicator = { tabPositions -> + TabRowDefaults.PrimaryIndicator( + width = 56.dp, + modifier = Modifier.tabIndicatorOffset(tabPositions[selectedTab]), + color = MaterialTheme.colorScheme.onPrimary, + ) + }, + divider = {}, + ) { + tabLabels.forEachIndexed { index, tabLabel -> + Tab( + modifier = Modifier + .height(48.dp) + .onGloballyPositioned { coordinates -> + tabWidths.add(index, coordinates.size.width) + }, + selected = selectedTab == index, + onClick = { + selectedTab = index + onTabClicked(index) + }, + ) { + Text( + text = tabLabel, + color = MaterialTheme.colorScheme.onPrimary, + style = MaterialTheme.typography.titleSmall, + ) + } + } + } + } +} diff --git a/aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/ui/DataSetDetailRow.kt b/aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/ui/DataSetDetailRow.kt new file mode 100644 index 0000000000..a17b5b54fa --- /dev/null +++ b/aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/ui/DataSetDetailRow.kt @@ -0,0 +1,96 @@ +package org.dhis2.mobile.aggregates.ui + +import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AccountTree +import androidx.compose.material.icons.filled.CalendarMonth +import androidx.compose.material.icons.filled.Category +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import org.dhis2.mobile.aggregates.model.DataSetDetails +import org.hisp.dhis.mobile.ui.designsystem.component.AssistChip +import org.hisp.dhis.mobile.ui.designsystem.component.Tag +import org.hisp.dhis.mobile.ui.designsystem.component.TagType +import org.hisp.dhis.mobile.ui.designsystem.theme.Spacing + +@Composable +internal fun DataSetDetails( + modifier: Modifier = Modifier, + editable: Boolean = false, + dataSetDetails: DataSetDetails, +) { + LazyRow( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = spacedBy(Spacing.Spacing8), + ) { + item { + if (editable) { + AssistChip( + label = dataSetDetails.dateLabel, + icon = { + Icon( + imageVector = Icons.Filled.CalendarMonth, + contentDescription = "", + ) + }, + onClick = { + /*not yet supported*/ + }, + ) + } else { + Tag( + label = dataSetDetails.dateLabel, + type = TagType.DEFAULT, + ) + } + } + + item { + if (editable) { + AssistChip( + label = dataSetDetails.orgUnitLabel, + icon = { + Icon( + imageVector = Icons.Filled.AccountTree, + contentDescription = "", + ) + }, + onClick = { + /*not yet supported*/ + }, + ) + } else { + Tag( + label = dataSetDetails.orgUnitLabel, + type = TagType.DEFAULT, + ) + } + } + + item { + if (editable) { + AssistChip( + label = dataSetDetails.catOptionComboLabel, + icon = { + Icon( + imageVector = Icons.Filled.Category, + contentDescription = "", + ) + }, + onClick = { + /*not yet supported*/ + }, + ) + } else { + Tag( + label = dataSetDetails.catOptionComboLabel, + type = TagType.DEFAULT, + ) + } + } + } +} diff --git a/aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/ui/DataSetTableScreen.kt b/aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/ui/DataSetTableScreen.kt index 57cf6fd2cb..8d1962b755 100644 --- a/aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/ui/DataSetTableScreen.kt +++ b/aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/ui/DataSetTableScreen.kt @@ -1,24 +1,350 @@ +@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) + package org.dhis2.mobile.aggregates.ui +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredHeight +import androidx.compose.foundation.layout.requiredWidth +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import org.dhis2.mobile.aggregates.model.DataSetDetails +import org.dhis2.mobile.aggregates.model.DataSetScreenState +import org.dhis2.mobile.aggregates.model.DataSetSection +import org.hisp.dhis.mobile.ui.designsystem.component.IconButton +import org.hisp.dhis.mobile.ui.designsystem.component.TopBar +import org.hisp.dhis.mobile.ui.designsystem.component.TopBarActionIcon +import org.hisp.dhis.mobile.ui.designsystem.component.TopBarDropdownMenuIcon +import org.hisp.dhis.mobile.ui.designsystem.theme.Radius +import org.hisp.dhis.mobile.ui.designsystem.theme.Spacing +@OptIn(ExperimentalMaterial3Api::class) +/** + * Data set table screen + * Shows the data set details and tables + * @param dataSetScreenState: Data set screen state + * @param onBackClicked: Callback function to be invoked when the back button is clicked + * */ @Composable -fun DataSetTableScreen() { +fun DataSetInstanceScreen( + dataSetScreenState: DataSetScreenState, + onBackClicked: () -> Unit, +) { + val onSectionSelected: (uid: String) -> Unit = {} Scaffold( - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .fillMaxSize(), + containerColor = MaterialTheme.colorScheme.primary, topBar = { + TopBar( + modifier = Modifier.fillMaxWidth(), + navigationIcon = { + TopBarActionIcon( + icon = Icons.AutoMirrored.Filled.ArrowBack, + tint = MaterialTheme.colorScheme.onPrimary, + onClick = onBackClicked, + ) + }, + actions = { + TopBarDropdownMenuIcon( + iconTint = MaterialTheme.colorScheme.onPrimary, + dropDownMenu = { showMenu, onDismissRequest -> + DropdownMenu( + expanded = showMenu, + onDismissRequest = onDismissRequest, + ) { + DropdownMenuItem( + text = { Text("Action 1") }, + onClick = {}, + leadingIcon = { + IconButton( + onClick = { + onDismissRequest() + }, + icon = { + Icon( + imageVector = Icons.Outlined.Delete, + contentDescription = "Edit Button", + ) + }, + ) + }, + ) + } + }, + ) + }, + title = { + Text( + dataSetScreenState.dataSetDetails.titleLabel, + color = MaterialTheme.colorScheme.onPrimary, + style = MaterialTheme.typography.titleLarge, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + }, + colors = TopAppBarDefaults.topAppBarColors().copy( + containerColor = MaterialTheme.colorScheme.primary, + titleContentColor = MaterialTheme.colorScheme.onPrimary, + ), + ) }, - ) { paddingValues -> - Column( - modifier = Modifier.fillMaxWidth() - .padding(paddingValues), + ) { + if (dataSetScreenState.useTwoPane) { + TwoPaneScreen( + modifier = Modifier.fillMaxSize().padding(it), + primaryPaneWeight = 0.7f, + primaryPane = { + DataSetTableContent( + dataSetDetails = dataSetScreenState.dataSetDetails, + ) + }, + secondaryPane = { + SectionColumn( + modifier = Modifier + .fillMaxSize() + .padding( + top = 0.dp, + start = 16.dp, + end = 16.dp, + bottom = 16.dp, + ), + dataSetSections = dataSetScreenState.dataSetSections, + onSectionSelected = onSectionSelected, + ) + }, + ) + } else { + DataSetSinglePane( + modifier = Modifier.fillMaxSize().padding(it), + dataSetSections = dataSetScreenState.dataSetSections, + dataSetDetails = dataSetScreenState.dataSetDetails, + onSectionSelected = onSectionSelected, + ) + } + } +} + +/** + * Data set single pane layout + * Default layout for portrait devices + * @param modifier: Modifier for styling + * @param dataSetSections: List of data set sections + * @param dataSetDetails: Data set details + * @param onSectionSelected: Callback function to be invoked when a section is selected + * */ +@OptIn(ExperimentalFoundationApi::class) +@Composable +private fun DataSetSinglePane( + modifier: Modifier = Modifier, + dataSetSections: List, + dataSetDetails: DataSetDetails, + onSectionSelected: (uid: String) -> Unit, +) { + LazyColumn( + modifier = modifier + .fillMaxSize() + .background( + color = MaterialTheme.colorScheme.primary, + ) + .clip( + RoundedCornerShape( + topStart = Radius.L, + topEnd = Radius.L, + ), + ), + ) { + item(key = "section_tabs") { + SectionTabs( + dataSetSections = dataSetSections, + onSectionSelected = onSectionSelected, + ) + } + + item(key = "details") { + DataSetDetails( + modifier = Modifier + .height(68.dp) + .fillMaxWidth() + .background( + color = MaterialTheme.colorScheme.background, + shape = RoundedCornerShape( + topStart = Radius.L, + topEnd = Radius.L, + ), + ).padding(horizontal = 16.dp), + dataSetDetails = dataSetDetails, + ) + } + + repeat(10) { + stickyHeader { + Text( + modifier = Modifier + .fillMaxWidth() + .background(color = MaterialTheme.colorScheme.background) + .padding(horizontal = 16.dp, vertical = 24.dp), + text = "Table $it", + ) + } + item { + DataSetTables( + modifier = Modifier + .background(color = MaterialTheme.colorScheme.background) + .height(200.dp) + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 24.dp), + ) + } + } + } +} + +@Composable +private fun SectionTabs( + modifier: Modifier = Modifier, + dataSetSections: List, + onSectionSelected: (uid: String) -> Unit, +) { + val tabLabels = remember { + dataSetSections.map { it.title } + } + AdaptiveTabRow( + modifier = modifier + .height(48.dp) + .fillMaxWidth(), + tabLabels = tabLabels, + onTabClicked = { selectedTabIndex -> + onSectionSelected(dataSetSections[selectedTabIndex].uid) + }, + ) +} + +@Composable +private fun SectionColumn( + modifier: Modifier = Modifier, + dataSetSections: List, + onSectionSelected: (String) -> Unit, +) { + var selectedSection by remember { mutableStateOf(0) } + val indicatorVerticalOffset by animateDpAsState( + targetValue = (selectedSection * 48).dp, + label = "", + ) + + Box( + modifier = modifier + .background( + color = MaterialTheme.colorScheme.surface, + shape = MaterialTheme.shapes.large, + ), + ) { + LazyColumn( + modifier = Modifier.fillMaxSize(), ) { + itemsIndexed(dataSetSections) { index, item -> + Box( + modifier = Modifier + .fillMaxWidth() + .height(48.dp) + .clickable { + selectedSection = index + onSectionSelected(item.uid) + }, + ) { + Text( + modifier = Modifier.align(Alignment.CenterStart) + .padding(horizontal = 16.dp), + text = item.title, + color = if (selectedSection == index) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.titleSmall, + ) + } + } } + Spacer( + Modifier + .align(Alignment.TopEnd) + .offset(x = 3.dp, y = indicatorVerticalOffset) + .requiredHeight(48.dp) + .requiredWidth(6.dp) + .background( + color = MaterialTheme.colorScheme.primary, + shape = RoundedCornerShape(3.0.dp), + ), + ) } } + +@Composable +private fun DataSetTableContent( + modifier: Modifier = Modifier, + dataSetDetails: DataSetDetails, +) { + LazyColumn( + modifier = modifier + .background( + color = MaterialTheme.colorScheme.background, + shape = RoundedCornerShape(topStart = Radius.L, topEnd = Radius.L), + ) + .padding(horizontal = 16.dp, vertical = 24.dp), + verticalArrangement = spacedBy(Spacing.Spacing24), + ) { + item { + DataSetDetails( + modifier.padding(horizontal = 16.dp), + dataSetDetails = dataSetDetails, + ) + } + + items(count = 10) { + DataSetTables( + modifier = Modifier.height(200.dp).fillMaxWidth(), + ) + } + } +} + +@Composable +private fun DataSetTables(modifier: Modifier = Modifier) { + Column( + modifier = modifier + .background( + color = MaterialTheme.colorScheme.surface, + shape = MaterialTheme.shapes.small, + ), + ) { } +} diff --git a/aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/ui/ScreenWidth.kt b/aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/ui/ScreenWidth.kt new file mode 100644 index 0000000000..5eb69e80fa --- /dev/null +++ b/aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/ui/ScreenWidth.kt @@ -0,0 +1,7 @@ +package org.dhis2.mobile.aggregates.ui + +import androidx.compose.runtime.Composable +import androidx.compose.ui.unit.Dp + +@Composable +expect fun getScreenWidth(): Dp diff --git a/aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/ui/TwoPaneScreen.kt b/aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/ui/TwoPaneScreen.kt new file mode 100644 index 0000000000..e43bb97850 --- /dev/null +++ b/aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/ui/TwoPaneScreen.kt @@ -0,0 +1,40 @@ +package org.dhis2.mobile.aggregates.ui + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +/** + * Two pane screen + * Divides the screen in two parts for extended device screens + * @param modifier: Modifier for styling + * @param primaryPaneWeight: Weight of the primary pane + * @param primaryPane: Primary pane content + * @param secondaryPane: Secondary pane content + * */ +@Composable +fun TwoPaneScreen( + modifier: Modifier = Modifier, + primaryPaneWeight: Float, + primaryPane: @Composable () -> Unit, + secondaryPane: @Composable () -> Unit, +) { + Row(modifier = modifier) { + Box( + modifier = Modifier + .fillMaxHeight() + .weight(1f - primaryPaneWeight), + ) { + secondaryPane() + } + Box( + modifier = Modifier + .fillMaxHeight() + .weight(primaryPaneWeight), + ) { + primaryPane() + } + } +} diff --git a/aggregates/src/desktopMain/kotlin/org/dhis2/mobile/aggregates/ui/ScreenWidth.desktop.kt b/aggregates/src/desktopMain/kotlin/org/dhis2/mobile/aggregates/ui/ScreenWidth.desktop.kt new file mode 100644 index 0000000000..df55febc3c --- /dev/null +++ b/aggregates/src/desktopMain/kotlin/org/dhis2/mobile/aggregates/ui/ScreenWidth.desktop.kt @@ -0,0 +1,13 @@ +package org.dhis2.mobile.aggregates.ui + +import androidx.compose.runtime.Composable +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.platform.LocalWindowInfo +import androidx.compose.ui.unit.dp + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +actual fun getScreenWidth() = LocalWindowInfo.current + .containerSize + .width + .dp diff --git a/aggregates/src/iosMain/kotlin/org/dhis2/mobile/aggregates/ui/ScreenWidth.kt b/aggregates/src/iosMain/kotlin/org/dhis2/mobile/aggregates/ui/ScreenWidth.kt new file mode 100644 index 0000000000..673ba2f3c2 --- /dev/null +++ b/aggregates/src/iosMain/kotlin/org/dhis2/mobile/aggregates/ui/ScreenWidth.kt @@ -0,0 +1,13 @@ +package org.dhis2.mobile.aggregates.ui + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +actual fun getScreenWidth(): Dp = LocalWindowInfo.current + .containerSize + .width + .dp \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 715c9df975..626bba40df 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -158,6 +158,7 @@ + false + WindowWidthSizeClass.Compact -> false + WindowWidthSizeClass.Expanded -> true + else -> false + } + + val screenState = previewDataSetScreenState(useTwoPane, 3) + + DataSetInstanceScreen(screenState) { + onBackPressedDispatcher.onBackPressed() + } + } + } + } +} diff --git a/app/src/main/java/org/dhis2/usescases/datasets/datasetDetail/datasetList/DataSetListFragment.kt b/app/src/main/java/org/dhis2/usescases/datasets/datasetDetail/datasetList/DataSetListFragment.kt index 476e90bae2..0abb155a52 100644 --- a/app/src/main/java/org/dhis2/usescases/datasets/datasetDetail/datasetList/DataSetListFragment.kt +++ b/app/src/main/java/org/dhis2/usescases/datasets/datasetDetail/datasetList/DataSetListFragment.kt @@ -8,9 +8,12 @@ import androidx.fragment.app.viewModels import com.google.android.material.snackbar.Snackbar import org.dhis2.R import org.dhis2.commons.Constants +import org.dhis2.commons.featureconfig.data.FeatureConfigRepository +import org.dhis2.commons.featureconfig.model.Feature import org.dhis2.commons.sync.OnDismissListener import org.dhis2.commons.sync.SyncContext import org.dhis2.databinding.FragmentDataSetListBinding +import org.dhis2.usescases.datasets.dataSetTable.DataSetInstanceActivity import org.dhis2.usescases.datasets.dataSetTable.DataSetTableActivity import org.dhis2.usescases.datasets.datasetDetail.DataSetDetailActivity import org.dhis2.usescases.datasets.datasetDetail.DataSetDetailModel @@ -37,6 +40,9 @@ class DataSetListFragment : FragmentGlobalAbstract() { @Inject lateinit var datasetCardMapper: DatasetCardMapper + @Inject + lateinit var featureConfig: FeatureConfigRepository + private val viewModel: DataSetListViewModel by viewModels { viewModelFactory } override fun onCreateView( @@ -114,7 +120,12 @@ class DataSetListFragment : FragmentGlobalAbstract() { putString(Constants.DATA_SET_UID, dataSetUid) putBoolean(Constants.ACCESS_DATA, accessWriteData) } - startActivity(DataSetTableActivity::class.java, bundle, false, false, null) + + if (featureConfig.isFeatureEnable(Feature.COMPOSE_AGGREGATES_SCREEN)) { + startActivity(DataSetInstanceActivity::class.java, bundle, false, false, null) + } else { + startActivity(DataSetTableActivity::class.java, bundle, false, false, null) + } } private fun showSyncDialog(dataSet: DataSetDetailModel) { diff --git a/app/src/main/java/org/dhis2/usescases/general/SessionManagerActivity.kt b/app/src/main/java/org/dhis2/usescases/general/SessionManagerActivity.kt index fecc90d4d5..415fa6f76f 100644 --- a/app/src/main/java/org/dhis2/usescases/general/SessionManagerActivity.kt +++ b/app/src/main/java/org/dhis2/usescases/general/SessionManagerActivity.kt @@ -131,7 +131,7 @@ abstract class SessionManagerActivity : AppCompatActivity(), ActivityResultObser override fun onRequestPermissionsResult( requestCode: Int, - permissions: Array, + permissions: Array, grantResults: IntArray, ) { if (activityResultObserver != null) { diff --git a/app/src/main/java/org/dhis2/usescases/login/ui/LoginScreen.kt b/app/src/main/java/org/dhis2/usescases/login/ui/LoginScreen.kt index 0e0bf01898..904e9be665 100644 --- a/app/src/main/java/org/dhis2/usescases/login/ui/LoginScreen.kt +++ b/app/src/main/java/org/dhis2/usescases/login/ui/LoginScreen.kt @@ -30,13 +30,14 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.constraintlayout.compose.ConstraintLayout import org.dhis2.R -import org.hisp.dhis.mobile.ui.designsystem.resource.provideFontResource import org.hisp.dhis.mobile.ui.designsystem.theme.DHIS2Theme import org.hisp.dhis.mobile.ui.designsystem.theme.SurfaceColor @@ -118,7 +119,7 @@ fun LoginTopBar( style = TextStyle( fontSize = 16.sp, lineHeight = 24.sp, - fontFamily = provideFontResource("rubik_regular"), + fontFamily = FontFamily(Font(R.font.rubik_regular)), fontWeight = FontWeight.Normal, color = Color.Black, letterSpacing = 0.5.sp, @@ -140,7 +141,7 @@ fun LoginTopBar( style = TextStyle( fontSize = 12.sp, lineHeight = 16.sp, - fontFamily = provideFontResource("rubik_regular"), + fontFamily = FontFamily(Font(R.font.rubik_regular)), fontWeight = FontWeight.Normal, color = SurfaceColor.ContainerHighest, letterSpacing = 0.4.sp, diff --git a/app/src/main/java/org/dhis2/usescases/qrScanner/ScanActivity.kt b/app/src/main/java/org/dhis2/usescases/qrScanner/ScanActivity.kt index e78cbbc67f..2bd79b7532 100644 --- a/app/src/main/java/org/dhis2/usescases/qrScanner/ScanActivity.kt +++ b/app/src/main/java/org/dhis2/usescases/qrScanner/ScanActivity.kt @@ -93,7 +93,7 @@ class ScanActivity : ActivityGlobalAbstract() { override fun onRequestPermissionsResult( requestCode: Int, - permissions: Array, + permissions: Array, grantResults: IntArray, ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) diff --git a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/ui/SearchTEUi.kt b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/ui/SearchTEUi.kt index 174ca0bd22..4128321665 100644 --- a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/ui/SearchTEUi.kt +++ b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/ui/SearchTEUi.kt @@ -30,7 +30,7 @@ import androidx.compose.material.IconButton import androidx.compose.material.LocalTextStyle import androidx.compose.material.OutlinedButton import androidx.compose.material.Text -import androidx.compose.material.ripple.rememberRipple +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -198,7 +198,7 @@ fun SearchButtonWithQuery( .clickable( onClick = onClick, interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple( + indication = ripple( true, color = SurfaceColor.Primary, ), diff --git a/app/src/main/java/org/dhis2/utils/granularsync/SMSSenderHelper.kt b/app/src/main/java/org/dhis2/utils/granularsync/SMSSenderHelper.kt index a59b8e4f9e..317bc82715 100644 --- a/app/src/main/java/org/dhis2/utils/granularsync/SMSSenderHelper.kt +++ b/app/src/main/java/org/dhis2/utils/granularsync/SMSSenderHelper.kt @@ -82,7 +82,7 @@ class SMSSenderHelper( .show(fragmentManager, BottomSheetDialogUiModel::class.java.simpleName) } - private fun createSMSIntent(message: String, smsToNumber: String): Intent? { + private fun createSMSIntent(message: String, smsToNumber: String): Intent { val uri = Uri.parse("smsto:$smsToNumber") val intent = Intent(Intent.ACTION_SENDTO).apply { data = uri diff --git a/build.gradle.kts b/build.gradle.kts index c72f4db7eb..63b1cb794f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,8 +3,8 @@ import java.util.Locale // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { - google() maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") } + google() } dependencies { classpath(libs.gradlePlugin) @@ -62,13 +62,13 @@ allprojects { } repositories { + maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") } google() mavenCentral() maven { url = uri("https://maven.google.com") } maven { url = uri("https://jitpack.io") } - maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") } } apply(plugin = "org.jlleitschuh.gradle.ktlint") @@ -94,6 +94,7 @@ allprojects { filter { excludes.add("**/*.kts") exclude { element -> element.file.path.contains("androidTest") } + exclude { element -> element.file.path.contains("generated") } exclude { element -> element.file.path.contains("dhis2-android-sdk") } } } diff --git a/commons/src/main/java/org/dhis2/commons/ActivityResultObserver.kt b/commons/src/main/java/org/dhis2/commons/ActivityResultObserver.kt index c9aa3fb309..e462b097b3 100644 --- a/commons/src/main/java/org/dhis2/commons/ActivityResultObserver.kt +++ b/commons/src/main/java/org/dhis2/commons/ActivityResultObserver.kt @@ -6,7 +6,7 @@ interface ActivityResultObserver { fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) fun onRequestPermissionsResult( requestCode: Int, - permissions: Array, + permissions: Array, grantResults: IntArray, ) } diff --git a/commons/src/main/java/org/dhis2/commons/featureconfig/data/FeatureConfigRepositoryImpl.kt b/commons/src/main/java/org/dhis2/commons/featureconfig/data/FeatureConfigRepositoryImpl.kt index 0054f15090..8b0ed15655 100644 --- a/commons/src/main/java/org/dhis2/commons/featureconfig/data/FeatureConfigRepositoryImpl.kt +++ b/commons/src/main/java/org/dhis2/commons/featureconfig/data/FeatureConfigRepositoryImpl.kt @@ -60,6 +60,7 @@ class FeatureConfigRepositoryImpl @Inject constructor( return when (feature) { Feature.AUTO_LOGOUT -> null Feature.RESPONSIVE_HOME -> FeatureOptions.ResponsiveHome(totalItems = getResponsiveHomeTotalItems()) + Feature.COMPOSE_AGGREGATES_SCREEN -> null } } diff --git a/commons/src/main/java/org/dhis2/commons/featureconfig/model/Feature.kt b/commons/src/main/java/org/dhis2/commons/featureconfig/model/Feature.kt index 7b5e976a50..68e4ae43ea 100644 --- a/commons/src/main/java/org/dhis2/commons/featureconfig/model/Feature.kt +++ b/commons/src/main/java/org/dhis2/commons/featureconfig/model/Feature.kt @@ -3,4 +3,5 @@ package org.dhis2.commons.featureconfig.model enum class Feature(val description: String) { AUTO_LOGOUT("automatic log out"), RESPONSIVE_HOME("responsive home"), + COMPOSE_AGGREGATES_SCREEN("compose aggregates screen"), } diff --git a/dhis_android_analytics/src/main/java/dhis2/org/analytics/charts/ui/IndicatorViewHolder.kt b/dhis_android_analytics/src/main/java/dhis2/org/analytics/charts/ui/IndicatorViewHolder.kt index 272e71ee5b..37e0c997ba 100644 --- a/dhis_android_analytics/src/main/java/dhis2/org/analytics/charts/ui/IndicatorViewHolder.kt +++ b/dhis_android_analytics/src/main/java/dhis2/org/analytics/charts/ui/IndicatorViewHolder.kt @@ -7,7 +7,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size -import androidx.compose.material.ripple.rememberRipple +import androidx.compose.material3.ripple import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -40,7 +40,7 @@ class IndicatorViewHolder( if (programIndicatorModel.programIndicator?.description() != null) { Modifier.clickable( interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(), + indication = ripple(), onClick = { showDescription(programIndicatorModel.programIndicator) }, ) } else { @@ -60,7 +60,7 @@ class IndicatorViewHolder( if (programIndicatorModel.programIndicator?.description() != null) { Modifier.clickable( interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(), + indication = ripple(), onClick = { showDescription(programIndicatorModel.programIndicator) }, ) } else { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 792b94ed95..8879dcb80f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -sdk = "34" +sdk = "35" minSdk = "21" vCode = "138" vName = "3.1.1" @@ -84,7 +84,9 @@ preference_ktx = "1.2.1" uiautomator = "2.2.0" maplibre = "10.2.0" material3WindowSize = "1.2.0" +material3WindowSizeCompose = "1.7.1" material3AdaptiveAndroid = "1.0.0" +uiTooling = "1.7.6" [libraries] gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "gradle" } kotlinPlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } @@ -126,9 +128,10 @@ androidx-exifinterface = { group = "androidx.exifinterface", name = "exifinterfa androidx-preferenceKtx = { group = "androidx.preference", name = "preference-ktx", version.ref = "preference_ktx" } androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" } androidx-material3-window = { group = "androidx.compose.material3", name = "material3-window-size-class-android", version.ref = "material3WindowSize" } +compose-material3-window = { group = "org.jetbrains.compose.material3", name = "material3-window-size-class", version.ref = "material3WindowSizeCompose" } androidx-material3-adaptative-android = { group = "androidx.compose.material3.adaptive", name = "adaptive-android", version.ref = "material3AdaptiveAndroid" } androidx-dynamicanimation = { group = "androidx.dynamicanimation", name = "dynamicanimation", version.ref = "dynamicanimation" } -androidx-biometric = { group = "androidx.biometric", name = "biometric", version = "1.1.0"} +androidx-biometric = { group = "androidx.biometric", name = "biometric", version = "1.1.0" } kotlin-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxserialization" } google-guava = { group = "com.google.guava", name = "guava", version.ref = "guava" } google-auth = { group = "com.google.android.gms", name = "play-services-auth", version = "20.6.0" } @@ -165,7 +168,7 @@ analytics-rxlint = { group = "nl.littlerobots.rxlint", name = "rxlint", version. analytics-customactivityoncrash = { group = "cat.ereza", name = "customactivityoncrash", version.ref = "crashactivity" } analytics-timber = { group = "com.jakewharton.timber", name = "timber", version.ref = "timber" } analytics-sentry = { group = "io.sentry", name = "sentry-android", version.ref = "sentry" } -analytics-sentry-compose = {group="io.sentry", name="sentry-compose-android", version.ref = "sentry"} +analytics-sentry-compose = { group = "io.sentry", name = "sentry-compose-android", version.ref = "sentry" } security-rootbeer = { group = "com.scottyab", name = "rootbeer-lib", version.ref = "root" } security-openId = { group = "net.openid", name = "appauth", version.ref = "openid" } security-conscrypt = { group = "org.conscrypt", name = "conscrypt-android", version.ref = "conscrypt" } @@ -204,10 +207,11 @@ dispatcher-dispatchBOM = { group = "com.rickbusarow.dispatch", name = "dispatch- dispatcher-dispatchCore = { group = "com.rickbusarow.dispatch", name = "dispatch-core" } dispatcher-dispatchEspresso = { group = "com.rickbusarow.dispatch", name = "dispatch-android-espresso", version = "1.0.0-beta10" } deprecated-autoValueParcel = { group = "com.ryanharter.auto.value", name = "auto-value-parcel", version.ref = "autovalueparcel" } +androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "uiTooling" } [plugins] kotlin-compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } [bundles] -uicomponents-implementation = ["androidx-coreKtx", "androidx-material3", "androidx-material3-window","androidx-material3-adaptative-android", "androidx-material3-adaptative-android", "google-material", "lottie-compose", "dhis2-mobile-designsystem"] +uicomponents-implementation = ["androidx-coreKtx", "androidx-material3", "androidx-material3-window", "androidx-material3-adaptative-android", "androidx-material3-adaptative-android", "google-material", "lottie-compose", "dhis2-mobile-designsystem"] uicomponents-api = ["dhis2-mobile-designsystem", "androidx-compose-constraintlayout", "androidx-compose-preview", "androidx-compose-ui", "google-material-themeadapter", "google-material3-themeadapter"] uicomponents-debugapi = ["androidx-compose-uitooling"] uicomponents-androidtest = ["test-junit-ext"] @@ -216,7 +220,7 @@ analytics-api = ["github-charts"] analytics-kapt = ["dagger-compiler"] analytics-test = ["test-mockitoCore", "test-mockitoInline", "test-mockitoKotlin", "test-kotlinCoroutines", "test-archCoreTesting"] form-test = ["test-mockitoCore", "test-mockitoInline", "test-mockitoKotlin", "test-testCore", "test-archCoreTesting", "test-kotlinCoroutines"] -map-test = ["test-mockitoCore", "test-mockitoInline", "test-mockitoKotlin","test-turbine", "test-archCoreTesting", "test-kotlinCoroutines"] +map-test = ["test-mockitoCore", "test-mockitoInline", "test-mockitoKotlin", "test-turbine", "test-archCoreTesting", "test-kotlinCoroutines"] table-implementation = ["kotlin-serialization-json", "androidx-appcompat", "androidx-activity-compose", "androidx-compose", "androidx-compose-constraintlayout", "androidx-compose-preview", "androidx-compose-ui", "androidx-compose-livedata"] table-debugImplementation = ["androidx-compose-uitooling", "test-ui-test-manifest"] table-test = ["test-junit"] diff --git a/stock-usecase/src/main/java/org/dhis2/android/rtsm/ui/base/BaseActivity.kt b/stock-usecase/src/main/java/org/dhis2/android/rtsm/ui/base/BaseActivity.kt index a278d1ec97..e3482fe491 100644 --- a/stock-usecase/src/main/java/org/dhis2/android/rtsm/ui/base/BaseActivity.kt +++ b/stock-usecase/src/main/java/org/dhis2/android/rtsm/ui/base/BaseActivity.kt @@ -180,7 +180,7 @@ abstract class BaseActivity : AppCompatActivity() { override fun onRequestPermissionsResult( requestCode: Int, - permissions: Array, + permissions: Array, grantResults: IntArray, ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults)