From 75b19c7d9026224b7038b57b2a4f111bfccb79e3 Mon Sep 17 00:00:00 2001 From: Stilianos Tzouvaras Date: Fri, 21 Feb 2025 22:49:51 +0200 Subject: [PATCH] Refactor: Removed ContentTitle Composable and updated UI - Removed the `ContentTitle` composable and integrated its functionality into `ErrorTitle` in `ContentError`. - Updated `ViewDocumentScreen` to use a `Box` layout for better content management. - Adjusted `ViewDocumentScreen` to leverage `MediumTopAppBar` when a title is present. - Refactored `ContentScreen` to utilize `MediumTopAppBar` and dynamically show or hide title based on the state. - Added `ContentErrorConfig` for error display. - Replaced `ContentTitle` with `ErrorTitle` in `OptionsSelectionScreen`. - Updated version to `0.1.4`. --- gradle.properties | 2 +- .../ui/component/content/ContentError.kt | 79 ++++++++++++++-- .../ui/component/content/ContentScreen.kt | 76 ++++++++++++---- .../ui/component/content/ContentTitle.kt | 90 ------------------- .../OptionsSelectionScreen.kt | 4 +- .../ui/view_document/ViewDocumentScreen.kt | 61 ++++--------- 6 files changed, 147 insertions(+), 165 deletions(-) delete mode 100644 rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/presentation/ui/component/content/ContentTitle.kt diff --git a/gradle.properties b/gradle.properties index 06ea5db..2fe82f8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -25,7 +25,7 @@ TARGET_SDK_VERSION=35 MIN_SDK_VERSION=28 # PROJECT PROPERTIES -VERSION_NAME=0.1.3 +VERSION_NAME=0.1.4 NAMESPACE=eu.europa.ec.eudi.rqesui GROUP=eu.europa.ec.eudi diff --git a/rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/presentation/ui/component/content/ContentError.kt b/rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/presentation/ui/component/content/ContentError.kt index 1c89e08..03dffab 100644 --- a/rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/presentation/ui/component/content/ContentError.kt +++ b/rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/presentation/ui/component/content/ContentError.kt @@ -16,24 +16,36 @@ package eu.europa.ec.eudi.rqesui.presentation.ui.component.content +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import eu.europa.ec.eudi.rqesui.domain.entities.localization.LocalizableKey import eu.europa.ec.eudi.rqesui.infrastructure.provider.ResourceProvider import eu.europa.ec.eudi.rqesui.presentation.ui.component.preview.PreviewTheme import eu.europa.ec.eudi.rqesui.presentation.ui.component.preview.ThemeModePreviews import eu.europa.ec.eudi.rqesui.presentation.ui.component.utils.SIZE_MEDIUM +import eu.europa.ec.eudi.rqesui.presentation.ui.component.utils.VSpacer import eu.europa.ec.eudi.rqesui.presentation.ui.component.wrap.WrapPrimaryButton import org.koin.compose.koinInject +internal data class ContentErrorConfig( + val errorTitle: String? = null, + val errorSubTitle: String? = null, + val onCancel: () -> Unit, + val onRetry: (() -> Unit)? = null +) + @Composable internal fun ContentError( config: ContentErrorConfig, @@ -45,7 +57,7 @@ internal fun ContentError( .fillMaxSize() .padding(paddingValues), ) { - ContentTitle( + ErrorTitle( title = config.errorTitle ?: resourceProvider.getLocalizedString(LocalizableKey.GenericErrorMessage), subtitle = config.errorSubTitle @@ -70,12 +82,65 @@ internal fun ContentError( } } -internal data class ContentErrorConfig( - val errorTitle: String? = null, - val errorSubTitle: String? = null, - val onCancel: () -> Unit, - val onRetry: (() -> Unit)? = null -) +@Composable +private fun ErrorTitle( + modifier: Modifier = Modifier, + title: String, + subtitle: String? = null, + titleStyle: TextStyle = MaterialTheme.typography.headlineSmall.copy( + color = MaterialTheme.colorScheme.onSurface + ), + subtitleStyle: TextStyle = MaterialTheme.typography.bodyMedium.copy( + color = MaterialTheme.colorScheme.onSurface + ), + subTitleMaxLines: Int = Int.MAX_VALUE, +) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.Top + ) { + Text( + modifier = Modifier.fillMaxWidth(), + text = title, + style = titleStyle, + ) + VSpacer.Large() + + subtitle?.let { safeSubtitle -> + Text( + modifier = Modifier.fillMaxWidth(), + text = safeSubtitle, + style = subtitleStyle, + maxLines = subTitleMaxLines, + overflow = TextOverflow.Ellipsis + ) + } + } +} + +@ThemeModePreviews +@Composable +private fun ErrorTitlePreview() { + PreviewTheme { + ErrorTitle( + modifier = Modifier.fillMaxWidth(), + title = "Title", + subtitle = "Subtitle" + ) + } +} + +@ThemeModePreviews +@Composable +private fun ContentTitleNoSubtitlePreview() { + PreviewTheme { + ErrorTitle( + modifier = Modifier.fillMaxWidth(), + title = "Title", + subtitle = null + ) + } +} /** * Preview composable of [ContentError]. diff --git a/rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/presentation/ui/component/content/ContentScreen.kt b/rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/presentation/ui/component/content/ContentScreen.kt index bb4f95b..20d9302 100644 --- a/rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/presentation/ui/component/content/ContentScreen.kt +++ b/rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/presentation/ui/component/content/ContentScreen.kt @@ -21,6 +21,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -28,9 +29,11 @@ import androidx.compose.material3.DropdownMenu import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FabPosition import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MediumTopAppBar import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarColors import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.minimumInteractiveComponentSize import androidx.compose.runtime.Composable @@ -212,29 +215,66 @@ private fun DefaultToolBar( keyboardController: SoftwareKeyboardController?, toolbarConfig: ToolbarConfig?, ) { - TopAppBar( - modifier = Modifier - .shadow(elevation = SPACING_SMALL.dp) - .takeIf { toolbarConfig?.hasShadow == true } ?: Modifier, - title = { - Text( - text = toolbarConfig?.title.orEmpty(), - color = MaterialTheme.colorScheme.onSurface, - maxLines = 2, - overflow = TextOverflow.Ellipsis, + + val isTitlePresent = !toolbarConfig?.title.isNullOrEmpty() + + val topAppBar: @Composable ( + modifier: Modifier, + title: @Composable () -> Unit, + navigationIcon: @Composable () -> Unit, + actions: @Composable RowScope.() -> Unit, + colors: TopAppBarColors + ) -> Unit = if (isTitlePresent) { + { modifier, title, navigationIcon, actions, colors -> + MediumTopAppBar( + modifier = modifier, + title = title, + navigationIcon = navigationIcon, + actions = actions, + colors = colors ) + } + } else { + { modifier, title, navigationIcon, actions, colors -> + TopAppBar( + modifier = modifier, + title = title, + navigationIcon = navigationIcon, + actions = actions, + colors = colors + ) + } + } + + val modifier = Modifier + .shadow(elevation = SPACING_SMALL.dp) + .takeIf { toolbarConfig?.hasShadow == true } ?: Modifier + + val colors = TopAppBarDefaults.topAppBarColors().copy( + containerColor = MaterialTheme.colorScheme.surface + ) + + topAppBar( + modifier, + { + if (isTitlePresent) { + Text( + text = toolbarConfig.title, + color = MaterialTheme.colorScheme.onSurface, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } }, - navigationIcon = { - // Check if we should add back/close button. + { if (navigatableAction != ScreenNavigateAction.NONE) { - val navigationIcon = when (navigatableAction) { + val icon = when (navigatableAction) { ScreenNavigateAction.CANCELABLE -> AppIcons.Close else -> AppIcons.ArrowBack } - Row(modifier = Modifier.padding(start = SPACING_EXTRA_SMALL.dp)) { WrapIconButton( - iconData = navigationIcon, + iconData = icon, onClick = { onBack?.invoke() keyboardController?.hide() @@ -244,12 +284,10 @@ private fun DefaultToolBar( } } }, - // Add toolbar actions. - actions = { + { ToolBarActions(toolBarActions = toolbarConfig?.actions) }, - colors = TopAppBarDefaults.topAppBarColors() - .copy(containerColor = MaterialTheme.colorScheme.surface) + colors ) } diff --git a/rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/presentation/ui/component/content/ContentTitle.kt b/rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/presentation/ui/component/content/ContentTitle.kt deleted file mode 100644 index 82cff1b..0000000 --- a/rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/presentation/ui/component/content/ContentTitle.kt +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2023 European Commission - * - * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by the European - * Commission - subsequent versions of the EUPL (the "Licence"); You may not use this work - * except in compliance with the Licence. - * - * You may obtain a copy of the Licence at: - * https://joinup.ec.europa.eu/software/page/eupl - * - * Unless required by applicable law or agreed to in writing, software distributed under - * the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF - * ANY KIND, either express or implied. See the Licence for the specific language - * governing permissions and limitations under the Licence. - */ - -package eu.europa.ec.eudi.rqesui.presentation.ui.component.content - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.style.TextOverflow -import eu.europa.ec.eudi.rqesui.presentation.ui.component.preview.PreviewTheme -import eu.europa.ec.eudi.rqesui.presentation.ui.component.preview.ThemeModePreviews -import eu.europa.ec.eudi.rqesui.presentation.ui.component.utils.VSpacer - -@Composable -internal fun ContentTitle( - modifier: Modifier = Modifier, - title: String, - subtitle: String? = null, - titleStyle: TextStyle = MaterialTheme.typography.headlineSmall.copy( - color = MaterialTheme.colorScheme.onSurface - ), - subtitleStyle: TextStyle = MaterialTheme.typography.bodyMedium.copy( - color = MaterialTheme.colorScheme.onSurface - ), - subTitleMaxLines: Int = Int.MAX_VALUE, -) { - Column( - modifier = modifier, - verticalArrangement = Arrangement.Top - ) { - Text( - modifier = Modifier.fillMaxWidth(), - text = title, - style = titleStyle, - ) - VSpacer.Large() - - subtitle?.let { safeSubtitle -> - Text( - modifier = Modifier.fillMaxWidth(), - text = safeSubtitle, - style = subtitleStyle, - maxLines = subTitleMaxLines, - overflow = TextOverflow.Ellipsis - ) - } - } -} - -@ThemeModePreviews -@Composable -private fun ContentTitlePreview() { - PreviewTheme { - ContentTitle( - modifier = Modifier.fillMaxWidth(), - title = "Title", - subtitle = "Subtitle" - ) - } -} - -@ThemeModePreviews -@Composable -private fun ContentTitleNoSubtitlePreview() { - PreviewTheme { - ContentTitle( - modifier = Modifier.fillMaxWidth(), - title = "Title", - subtitle = null - ) - } -} \ No newline at end of file diff --git a/rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/presentation/ui/options_selection/OptionsSelectionScreen.kt b/rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/presentation/ui/options_selection/OptionsSelectionScreen.kt index 39f2171..ecbfcec 100644 --- a/rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/presentation/ui/options_selection/OptionsSelectionScreen.kt +++ b/rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/presentation/ui/options_selection/OptionsSelectionScreen.kt @@ -50,8 +50,8 @@ import eu.europa.ec.eudi.rqesui.presentation.extension.openUrl import eu.europa.ec.eudi.rqesui.presentation.ui.component.AppIcons import eu.europa.ec.eudi.rqesui.presentation.ui.component.SelectionItem import eu.europa.ec.eudi.rqesui.presentation.ui.component.content.ContentScreen -import eu.europa.ec.eudi.rqesui.presentation.ui.component.content.ContentTitle import eu.europa.ec.eudi.rqesui.presentation.ui.component.content.ScreenNavigateAction +import eu.europa.ec.eudi.rqesui.presentation.ui.component.content.ToolbarConfig import eu.europa.ec.eudi.rqesui.presentation.ui.component.preview.PreviewTheme import eu.europa.ec.eudi.rqesui.presentation.ui.component.preview.ThemeModePreviews import eu.europa.ec.eudi.rqesui.presentation.ui.component.utils.OneTimeLaunchedEffect @@ -88,6 +88,7 @@ internal fun OptionsSelectionScreen( navigatableAction = ScreenNavigateAction.CANCELABLE, onBack = { viewModel.setEvent(Event.Pop) }, contentErrorConfig = state.error, + toolBarConfig = ToolbarConfig(title = state.title), stickyBottom = { paddingValues -> if (state.isBottomBarButtonVisible) { state.bottomBarButtonAction?.let { safeButtonAction -> @@ -167,7 +168,6 @@ private fun Content( .padding(paddingValues) .verticalScroll(scrollState) ) { - ContentTitle(title = state.title) safeLet( state.documentSelectionItem, diff --git a/rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/presentation/ui/view_document/ViewDocumentScreen.kt b/rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/presentation/ui/view_document/ViewDocumentScreen.kt index 05c3dc6..0f90142 100644 --- a/rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/presentation/ui/view_document/ViewDocumentScreen.kt +++ b/rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/presentation/ui/view_document/ViewDocumentScreen.kt @@ -16,15 +16,10 @@ package eu.europa.ec.eudi.rqesui.presentation.ui.view_document -import android.widget.FrameLayout import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.calculateEndPadding -import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable @@ -32,7 +27,6 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.viewinterop.AndroidView import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavController @@ -44,7 +38,6 @@ import eu.europa.ec.eudi.rqesui.infrastructure.theme.values.divider import eu.europa.ec.eudi.rqesui.presentation.entities.config.ViewDocumentUiConfig import eu.europa.ec.eudi.rqesui.presentation.ui.component.AppIcons import eu.europa.ec.eudi.rqesui.presentation.ui.component.content.ContentScreen -import eu.europa.ec.eudi.rqesui.presentation.ui.component.content.ContentTitle import eu.europa.ec.eudi.rqesui.presentation.ui.component.content.ScreenNavigateAction import eu.europa.ec.eudi.rqesui.presentation.ui.component.content.ToolbarAction import eu.europa.ec.eudi.rqesui.presentation.ui.component.content.ToolbarConfig @@ -67,6 +60,7 @@ internal fun ViewDocumentScreen( isLoading = state.isLoading, toolBarConfig = rememberToolbarConfig( isSigned = state.config.isSigned, + state = state ), navigatableAction = ScreenNavigateAction.BACKABLE, onBack = { @@ -95,51 +89,24 @@ private fun Content( onNavigationRequested: (Effect.Navigation) -> Unit, paddingValues: PaddingValues, ) { - Column( + Box( modifier = Modifier .fillMaxSize() - .background(MaterialTheme.colorScheme.divider), - verticalArrangement = Arrangement.Top + .background(MaterialTheme.colorScheme.divider) + .padding(top = paddingValues.calculateTopPadding()) ) { - ContentTitle( - modifier = Modifier - .fillMaxWidth() - .background(MaterialTheme.colorScheme.surface) - .padding( - top = paddingValues.calculateTopPadding(), - start = paddingValues.calculateStartPadding(LocalLayoutDirection.current), - end = paddingValues.calculateEndPadding(LocalLayoutDirection.current), - ), - title = state.config.documentData.documentName, - ) - - // PDF Viewer (in a FrameLayout to prevent size conflicts) state.config.documentData.let { file -> AndroidView( - modifier = Modifier - .fillMaxWidth() - .weight(1f), // Allocate remaining space to the PDFView - factory = { context -> - FrameLayout(context).apply { - addView( - PDFView(context, null).apply { - layoutParams = FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.MATCH_PARENT - ) - } - ) - } - }, - update = { frameLayout -> - val pdfView = frameLayout.getChildAt(0) as? PDFView + modifier = Modifier.matchParentSize(), + factory = { context -> PDFView(context, null) }, + update = { pdfView -> pdfView - ?.fromUri(file.uri) - ?.enableAnnotationRendering(true) - ?.onLoad { + .fromUri(file.uri) + .enableAnnotationRendering(true) + .onLoad { onEventSend(Event.LoadingStateChanged(isLoading = false)) } - ?.load() + .load() } ) } @@ -157,6 +124,7 @@ private fun Content( @Composable private fun rememberToolbarConfig( isSigned: Boolean, + state: State ): ToolbarConfig { return remember(isSigned) { val toolbarActions = if (isSigned) { @@ -174,7 +142,8 @@ private fun rememberToolbarConfig( ToolbarConfig( actions = toolbarActions, - hasShadow = false, + hasShadow = true, + title = state.config.documentData.documentName ) } }