diff --git a/homeUi/screenshotTests/roborazzi/com.gravatar.app.homeUi.presentation.home.share.components.ExpandedQrCodeTest.expandedQrCode.png b/homeUi/screenshotTests/roborazzi/com.gravatar.app.homeUi.presentation.home.share.components.ExpandedQrCodeTest.expandedQrCode.png new file mode 100644 index 00000000..5062e9e7 Binary files /dev/null and b/homeUi/screenshotTests/roborazzi/com.gravatar.app.homeUi.presentation.home.share.components.ExpandedQrCodeTest.expandedQrCode.png differ diff --git a/homeUi/screenshotTests/roborazzi/com.gravatar.app.homeUi.presentation.home.share.components.ShareHeaderTest.shareHeader.png b/homeUi/screenshotTests/roborazzi/com.gravatar.app.homeUi.presentation.home.share.components.ShareHeaderTest.shareHeader.png index c04b5c16..e4fe19ea 100644 Binary files a/homeUi/screenshotTests/roborazzi/com.gravatar.app.homeUi.presentation.home.share.components.ShareHeaderTest.shareHeader.png and b/homeUi/screenshotTests/roborazzi/com.gravatar.app.homeUi.presentation.home.share.components.ShareHeaderTest.shareHeader.png differ diff --git a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/navigation/HomeNavigation.kt b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/navigation/HomeNavigation.kt index 4e8fb0c4..3d9d9efe 100644 --- a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/navigation/HomeNavigation.kt +++ b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/navigation/HomeNavigation.kt @@ -14,6 +14,7 @@ import kotlinx.serialization.Serializable internal fun HomeNavigation( navController: NavHostController, snackbarHostState: SnackbarHostState, + onShouldShowBottomBar: (Boolean) -> Unit, ) { NavHost( navController = navController, @@ -37,7 +38,8 @@ internal fun HomeNavigation( composable { ShareScreen( viewModelStoreOwner = navController.getBackStackEntry(HomeRoute::class), - snackbarHostState = snackbarHostState + snackbarHostState = snackbarHostState, + onShouldShowBottomBar = onShouldShowBottomBar, ) } } diff --git a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/HomeEvent.kt b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/HomeEvent.kt new file mode 100644 index 00000000..7fbb0a19 --- /dev/null +++ b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/HomeEvent.kt @@ -0,0 +1,5 @@ +package com.gravatar.app.homeUi.presentation.home + +internal sealed class HomeEvent { + data class ShowBottomBar(val show: Boolean) : HomeEvent() +} diff --git a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/HomeScreen.kt b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/HomeScreen.kt index db66821c..5c813344 100644 --- a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/HomeScreen.kt +++ b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/HomeScreen.kt @@ -51,7 +51,9 @@ internal fun HomeScreen( val uiState by viewModel.uiState.collectAsStateWithLifecycle() HomeScreen(uiState = uiState) { navController, snackbarHostState -> - HomeNavigation(navController, snackbarHostState) + HomeNavigation(navController, snackbarHostState) { showBottomBar -> + viewModel.onEvent(HomeEvent.ShowBottomBar(showBottomBar)) + } } } @@ -68,37 +70,39 @@ internal fun HomeScreen( contentWindowInsets = WindowInsets(0, 0, 0, 0), snackbarHost = { GravatarSnackbarHost(hostState = snackbarHostState) }, bottomBar = { - NavigationBar { - HomeDestination.allDestinations - .sortedBy { it.position } - .forEach { destination -> - NavigationBarItem( - icon = { - Icon( - painterResource(id = destination.iconRes), - contentDescription = stringResource(destination.labelRes) - ) - }, - label = { Text(stringResource(destination.labelRes)) }, - selected = destination.route == backStackEntry.value?.destination?.route, - onClick = { - if (backStackEntry.value?.destination?.route != destination.route) { - navController.navigate(destination) { - popUpTo(navController.graph.startDestinationId) { - saveState = true + if (uiState.showBottomBar) { + NavigationBar { + HomeDestination.allDestinations + .sortedBy { it.position } + .forEach { destination -> + NavigationBarItem( + icon = { + Icon( + painterResource(id = destination.iconRes), + contentDescription = stringResource(destination.labelRes) + ) + }, + label = { Text(stringResource(destination.labelRes)) }, + selected = destination.route == backStackEntry.value?.destination?.route, + onClick = { + if (backStackEntry.value?.destination?.route != destination.route) { + navController.navigate(destination) { + popUpTo(navController.graph.startDestinationId) { + saveState = true + } + launchSingleTop = true + restoreState = true } - launchSingleTop = true - restoreState = true } - } - }, - colors = NavigationBarItemDefaults.colors( - selectedIconColor = MaterialTheme.colorScheme.primary, - selectedTextColor = MaterialTheme.colorScheme.primary, - indicatorColor = Color.Transparent, + }, + colors = NavigationBarItemDefaults.colors( + selectedIconColor = MaterialTheme.colorScheme.primary, + selectedTextColor = MaterialTheme.colorScheme.primary, + indicatorColor = Color.Transparent, + ) ) - ) - } + } + } } } ) { innerPadding -> @@ -148,7 +152,7 @@ private fun HomeScreenPreview() { networkState = null ) ) { navController, snackbarHostState -> - HomeNavigation(navController, snackbarHostState) + HomeNavigation(navController, snackbarHostState) {} } } @@ -160,6 +164,6 @@ private fun HomeScreenNoInternetPreview() { networkState = NetworkState.DISCONNECTED ) ) { navController, snackbarHostState -> - HomeNavigation(navController, snackbarHostState) + HomeNavigation(navController, snackbarHostState) {} } } diff --git a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/HomeUiState.kt b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/HomeUiState.kt index a8d9d853..d0a7a1af 100644 --- a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/HomeUiState.kt +++ b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/HomeUiState.kt @@ -4,6 +4,7 @@ import com.gravatar.app.networkmonitor.NetworkState internal data class HomeUiState( val networkState: NetworkState? = null, + val showBottomBar: Boolean = true, ) { val noInternetBannerVisible: Boolean = networkState == NetworkState.DISCONNECTED diff --git a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/HomeViewModel.kt b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/HomeViewModel.kt index ef3ad534..050ea4fb 100644 --- a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/HomeViewModel.kt +++ b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/HomeViewModel.kt @@ -26,4 +26,14 @@ internal class HomeViewModel( } .launchIn(viewModelScope) } + + fun onEvent(homeEvent: HomeEvent) { + when (homeEvent) { + is HomeEvent.ShowBottomBar -> { + _uiState.update { currentState -> + currentState.copy(showBottomBar = homeEvent.show) + } + } + } + } } diff --git a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareAction.kt b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareAction.kt index 71edbc67..42817cc6 100644 --- a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareAction.kt +++ b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareAction.kt @@ -4,4 +4,5 @@ import java.io.File internal sealed class ShareAction { data class ShareVCard(val vCardFile: File) : ShareAction() + data class ShowBottomBar(val show: Boolean) : ShareAction() } diff --git a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareEvent.kt b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareEvent.kt index adb4ef6c..56216ef9 100644 --- a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareEvent.kt +++ b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareEvent.kt @@ -9,4 +9,6 @@ internal sealed class ShareEvent { data object OnPrivateInformationClicked : ShareEvent() data object OnDismissPrivateInformationDialog : ShareEvent() data object OnShareClick : ShareEvent() + data object OnExpandQrCodeClick : ShareEvent() + data object OnDismissExpandedQrCode : ShareEvent() } diff --git a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareScreen.kt b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareScreen.kt index ce2929d9..184e6274 100644 --- a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareScreen.kt +++ b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareScreen.kt @@ -2,6 +2,11 @@ package com.gravatar.app.homeUi.presentation.home.share import android.content.Context import android.content.Intent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -26,6 +31,7 @@ import androidx.lifecycle.repeatOnLifecycle import com.gravatar.app.design.theme.GravatarAppTheme import com.gravatar.app.homeUi.GravatarFileProvider import com.gravatar.app.homeUi.presentation.home.components.topbar.components.AboutAppDialog +import com.gravatar.app.homeUi.presentation.home.share.components.ExpandedQrCode import com.gravatar.app.homeUi.presentation.home.share.components.ItemDivider import com.gravatar.app.homeUi.presentation.home.share.components.PrivateInformationDialog import com.gravatar.app.homeUi.presentation.home.share.components.ShareHeader @@ -42,7 +48,8 @@ import java.io.File internal fun ShareScreen( viewModelStoreOwner: ViewModelStoreOwner, viewModel: ShareViewModel = koinViewModel(viewModelStoreOwner = viewModelStoreOwner), - snackbarHostState: SnackbarHostState + snackbarHostState: SnackbarHostState, + onShouldShowBottomBar: (Boolean) -> Unit, ) { val context = LocalContext.current val lifecycle = LocalLifecycleOwner.current @@ -56,6 +63,10 @@ internal fun ShareScreen( is ShareAction.ShareVCard -> { shareVCardFile(action.vCardFile, context) } + + is ShareAction.ShowBottomBar -> { + onShouldShowBottomBar(action.show) + } } } } @@ -86,6 +97,7 @@ internal fun ShareScreen(uiState: ShareUiState, onEvent: (ShareEvent) -> Unit) { }, vCardQrCodeData = uiState.vCardQrCodeData.exportToString(withPhoto = false), onShareClick = { onEvent(ShareEvent.OnShareClick) }, + onExpandQrCodeClick = { onEvent(ShareEvent.OnExpandQrCodeClick) }, modifier = Modifier .fillMaxWidth(), ) @@ -133,6 +145,20 @@ internal fun ShareScreen(uiState: ShareUiState, onEvent: (ShareEvent) -> Unit) { } ) } + + AnimatedVisibility( + visible = uiState.isQrCodeExpanded, + enter = slideInVertically() + fadeIn(), + exit = slideOutVertically() + fadeOut(), + ) { + ExpandedQrCode( + qrCodeData = uiState.vCardQrCodeData.exportToString(withPhoto = false), + avatarUrl = uiState.avatarUrl.orEmpty(), + onDismissRequest = { + onEvent(ShareEvent.OnDismissExpandedQrCode) + } + ) + } } private fun shareVCardFile( diff --git a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareUiState.kt b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareUiState.kt index 1a19f936..1aba36db 100644 --- a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareUiState.kt +++ b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareUiState.kt @@ -13,6 +13,7 @@ internal data class ShareUiState( val privateContactInfo: PrivateContactInfo = PrivateContactInfo.Default, val userSharePreferences: UserSharePreferences = UserSharePreferences.Default, val isPrivateInformationDialogVisible: Boolean = false, + val isQrCodeExpanded: Boolean = false, private val avatarDrawable: Drawable? = null, ) { val privateContactState = PrivateContactState( diff --git a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareViewModel.kt b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareViewModel.kt index beae6590..c3017a98 100644 --- a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareViewModel.kt +++ b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareViewModel.kt @@ -75,6 +75,28 @@ internal class ShareViewModel( is ShareEvent.OnPrivateInformationClicked -> showPrivateInformationDialog() is ShareEvent.OnDismissPrivateInformationDialog -> hidePrivateInformationDialog() ShareEvent.OnShareClick -> shareVCard() + ShareEvent.OnExpandQrCodeClick -> expandQrCode() + ShareEvent.OnDismissExpandedQrCode -> hideExpandedQrCode() + } + } + + private fun expandQrCode() { + viewModelScope.launch { + _uiState.update { currentState -> + currentState.copy(isQrCodeExpanded = true) + } + + _actions.send(ShareAction.ShowBottomBar(false)) + } + } + + private fun hideExpandedQrCode() { + viewModelScope.launch { + _uiState.update { currentState -> + currentState.copy(isQrCodeExpanded = false) + } + + _actions.send(ShareAction.ShowBottomBar(true)) } } diff --git a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/components/ExpandedQrCode.kt b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/components/ExpandedQrCode.kt new file mode 100644 index 00000000..894fd623 --- /dev/null +++ b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/components/ExpandedQrCode.kt @@ -0,0 +1,116 @@ +package com.gravatar.app.homeUi.presentation.home.share.components + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +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.unit.dp +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsControllerCompat +import com.gravatar.app.design.theme.GravatarAppTheme +import com.gravatar.app.homeUi.R +import com.gravatar.app.homeUi.presentation.findComponentActivity +import com.gravatar.app.homeUi.presentation.home.components.BlurredHeaderBackground +import com.gravatar.app.homeUi.presentation.home.profile.header.MENU_BUTTON_SIZE + +@Composable +fun ExpandedQrCode( + qrCodeData: String, + avatarUrl: String, + onDismissRequest: () -> Unit, +) { + BackHandler { + onDismissRequest() + } + HideNavigationBar() + Surface { + BlurredHeaderBackground( + avatarUrl = avatarUrl, + modifier = Modifier.fillMaxSize() + ) { + Row(verticalAlignment = Alignment.Top, modifier = Modifier.statusBarsPadding()) { + IconButton( + onClick = { + onDismissRequest() + }, + modifier = Modifier + .padding(16.dp) + .size(MENU_BUTTON_SIZE) + ) { + Image( + painter = painterResource(id = R.drawable.close_button), + contentDescription = stringResource(R.string.close_button), + ) + } + } + + Column( + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier + .align(Alignment.Center) + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) { + QrCode(qrCodeData = qrCodeData) + Text( + text = stringResource(R.string.share_tab_scan_qr_code), + style = MaterialTheme.typography.bodyMedium, + color = Color.White, + ) + } + } + } +} + +@Composable +private fun HideNavigationBar() { + val context = LocalContext.current + DisposableEffect(Unit) { + val window = context.findComponentActivity()?.window ?: return@DisposableEffect onDispose {} + val insetsController = WindowCompat.getInsetsController(window, window.decorView) + + insetsController.apply { + hide(WindowInsetsCompat.Type.navigationBars()) + systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + } + + onDispose { + insetsController.apply { + show(WindowInsetsCompat.Type.navigationBars()) + systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_DEFAULT + } + } + } +} + +@Preview +@Composable +fun ExpandedQrCodeDialogPreview() { + GravatarAppTheme { + ExpandedQrCode( + qrCodeData = "BEGIN:VCARD\nVERSION:3.0\nFN:John Doe\nEMAIL:", + avatarUrl = "https://gravatar.com/avatar/test", + onDismissRequest = {} + ) + } +} diff --git a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/components/QrCode.kt b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/components/QrCode.kt new file mode 100644 index 00000000..5c140ccd --- /dev/null +++ b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/components/QrCode.kt @@ -0,0 +1,57 @@ +package com.gravatar.app.homeUi.presentation.home.share.components + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.gravatar.app.design.theme.GravatarAppTheme +import com.gravatar.app.homeUi.R +import io.github.alexzhirkevich.qrose.options.QrBallShape +import io.github.alexzhirkevich.qrose.options.QrFrameShape +import io.github.alexzhirkevich.qrose.options.QrPixelShape +import io.github.alexzhirkevich.qrose.options.roundCorners +import io.github.alexzhirkevich.qrose.rememberQrCodePainter + +@Composable +fun QrCode( + qrCodeData: String, + modifier: Modifier = Modifier, +) { + val qrcodePainter: Painter = rememberQrCodePainter(qrCodeData) { + shapes { + ball = QrBallShape.roundCorners(.30f) + darkPixel = QrPixelShape.roundCorners() + frame = QrFrameShape.roundCorners(.15f) + } + } + + Image( + painter = qrcodePainter, + contentDescription = stringResource(R.string.share_tab_qr_code), + modifier = modifier + .background(Color.White, RoundedCornerShape(4.dp)) + .fillMaxWidth() + .aspectRatio( + ratio = 1f, + matchHeightConstraintsFirst = false, + ) + .padding(8.dp) + ) +} + +@Preview +@Composable +private fun QrCodePreview() { + GravatarAppTheme { + QrCode(qrCodeData = "BEGIN:VCARD\nVERSION:3.0\nN:Doe;John;;;\nFN:John Doe\nORG:Gravatar App\nEMAIL:") + } +} diff --git a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/components/ShareHeader.kt b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/components/ShareHeader.kt index 37c140f1..683cdd01 100644 --- a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/components/ShareHeader.kt +++ b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/components/ShareHeader.kt @@ -1,17 +1,14 @@ package com.gravatar.app.homeUi.presentation.home.share.components import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -23,7 +20,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -34,11 +30,6 @@ import com.gravatar.app.homeUi.R import com.gravatar.app.homeUi.presentation.home.components.BlurredHeaderBackground import com.gravatar.app.homeUi.presentation.home.components.topbar.TopBarPickerPopup import com.gravatar.app.homeUi.presentation.home.profile.header.MENU_BUTTON_SIZE -import io.github.alexzhirkevich.qrose.options.QrBallShape -import io.github.alexzhirkevich.qrose.options.QrFrameShape -import io.github.alexzhirkevich.qrose.options.QrPixelShape -import io.github.alexzhirkevich.qrose.options.roundCorners -import io.github.alexzhirkevich.qrose.rememberQrCodePainter @Composable internal fun ShareHeader( @@ -47,15 +38,9 @@ internal fun ShareHeader( modifier: Modifier = Modifier, onShareClick: () -> Unit = {}, onAboutAppClicked: () -> Unit = {}, + onExpandQrCodeClick: () -> Unit = {}, ) { var topBarMenuVisible by remember { mutableStateOf(false) } - val qrcodePainter: Painter = rememberQrCodePainter(vCardQrCodeData) { - shapes { - ball = QrBallShape.roundCorners(.30f) - darkPixel = QrPixelShape.roundCorners() - frame = QrFrameShape.roundCorners(.15f) - } - } BlurredHeaderBackground( avatarUrl = avatarUrl, @@ -74,18 +59,7 @@ internal fun ShareHeader( .padding(top = 6.dp) .weight(1f), ) { - Image( - painter = qrcodePainter, - contentDescription = null, - modifier = Modifier - .background(Color.White, RoundedCornerShape(4.dp)) - .fillMaxWidth() - .aspectRatio( - ratio = 1f, - matchHeightConstraintsFirst = false, - ) - .padding(6.dp) - ) + QrCode(vCardQrCodeData) Text( text = stringResource(R.string.share_tab_scan_qr_code), style = MaterialTheme.typography.bodyMedium, @@ -127,7 +101,19 @@ internal fun ShareHeader( ) { Image( painter = painterResource(id = R.drawable.share_button), - contentDescription = null + contentDescription = stringResource(R.string.share_tab_share_contact_information_button) + ) + } + IconButton( + onClick = { + onExpandQrCodeClick() + }, + modifier = Modifier + .size(MENU_BUTTON_SIZE) + ) { + Image( + painter = painterResource(id = R.drawable.expand_button), + contentDescription = stringResource(R.string.share_tab_expand_qr_code) ) } } diff --git a/homeUi/src/main/res/drawable/close_button.xml b/homeUi/src/main/res/drawable/close_button.xml new file mode 100644 index 00000000..f2426f6c --- /dev/null +++ b/homeUi/src/main/res/drawable/close_button.xml @@ -0,0 +1,13 @@ + + + + diff --git a/homeUi/src/main/res/drawable/expand_button.xml b/homeUi/src/main/res/drawable/expand_button.xml new file mode 100644 index 00000000..b2b0dda6 --- /dev/null +++ b/homeUi/src/main/res/drawable/expand_button.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/homeUi/src/main/res/values/strings.xml b/homeUi/src/main/res/values/strings.xml index f84d5146..aa181f4d 100644 --- a/homeUi/src/main/res/values/strings.xml +++ b/homeUi/src/main/res/values/strings.xml @@ -83,4 +83,8 @@ Got it Your email and phone number are only shared using the QR code. This information is not saved to your Gravatar profile, and is not publicly available. Private information + Share contact information in vCard file + Expand QR Code for sharing contact information + QR Code + Close diff --git a/homeUi/src/test/kotlin/com/gravatar/app/homeUi/presentation/home/HomeViewModelTest.kt b/homeUi/src/test/kotlin/com/gravatar/app/homeUi/presentation/home/HomeViewModelTest.kt index f21e8a83..0f06ca7a 100644 --- a/homeUi/src/test/kotlin/com/gravatar/app/homeUi/presentation/home/HomeViewModelTest.kt +++ b/homeUi/src/test/kotlin/com/gravatar/app/homeUi/presentation/home/HomeViewModelTest.kt @@ -75,4 +75,30 @@ class HomeViewModelTest { assertEquals(true, state.noInternetBannerVisible) } } + + @Test + fun `when ShowBottomBar event is received then showBottomBar state is updated accordingly`() = runTest { + // Given + viewModel = HomeViewModel(networkMonitor) + + // When - Hide bottom bar + viewModel.onEvent(HomeEvent.ShowBottomBar(show = false)) + advanceUntilIdle() + + // Then + viewModel.uiState.test { + val state = awaitItem() + assertEquals(false, state.showBottomBar) + } + + // When - Show bottom bar + viewModel.onEvent(HomeEvent.ShowBottomBar(show = true)) + advanceUntilIdle() + + // Then + viewModel.uiState.test { + val state = awaitItem() + assertEquals(true, state.showBottomBar) + } + } } diff --git a/homeUi/src/test/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareViewModelTest.kt b/homeUi/src/test/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareViewModelTest.kt index ad2f48f7..87d6a420 100644 --- a/homeUi/src/test/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareViewModelTest.kt +++ b/homeUi/src/test/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareViewModelTest.kt @@ -561,4 +561,39 @@ class ShareViewModelTest { verify { fileUtils.createVCardFile(eq(testProfile.displayName), any()) } } } + + @Test + fun `when OnExpandQrCodeClick event is triggered then isQrCodeExpanded is set to true and bottom bar is hidden`() = runTest { + viewModel.onEvent(ShareEvent.OnExpandQrCodeClick) + + viewModel.uiState.test { + // Initial state + assertFalse(awaitItem().isQrCodeExpanded) + + // Then - verify state update + assertTrue(awaitItem().isQrCodeExpanded) + } + + viewModel.actions.test { + assertFalse((awaitItem() as ShareAction.ShowBottomBar).show) + } + } + + @Test + fun `when OnDismissExpandedQrCode event is triggered then isQrCodeExpanded is set to false and bottom bar is shown`() = runTest { + viewModel.onEvent(ShareEvent.OnExpandQrCodeClick) + + viewModel.onEvent(ShareEvent.OnDismissExpandedQrCode) + advanceUntilIdle() + + // Test UI state update + viewModel.uiState.test { + assertFalse(awaitItem().isQrCodeExpanded) + } + + viewModel.actions.test { + assertFalse((awaitItem() as ShareAction.ShowBottomBar).show) + assertTrue((awaitItem() as ShareAction.ShowBottomBar).show) + } + } } diff --git a/homeUi/src/test/kotlin/com/gravatar/app/homeUi/presentation/home/share/components/ExpandedQrCodeTest.kt b/homeUi/src/test/kotlin/com/gravatar/app/homeUi/presentation/home/share/components/ExpandedQrCodeTest.kt new file mode 100644 index 00000000..61eb5e49 --- /dev/null +++ b/homeUi/src/test/kotlin/com/gravatar/app/homeUi/presentation/home/share/components/ExpandedQrCodeTest.kt @@ -0,0 +1,19 @@ +package com.gravatar.app.homeUi.presentation.home.share.components + +import com.gravatar.app.design.theme.GravatarAppTheme +import com.gravatar.app.testUtils.roborazzi.RoborazziTest +import org.junit.Test + +class ExpandedQrCodeTest : RoborazziTest() { + + @Test + fun expandedQrCode() = screenshotTest { + GravatarAppTheme { + ExpandedQrCode( + qrCodeData = "BEGIN:VCARD\nVERSION:3.0\nFN:Test User\nEND:VCARD", + avatarUrl = "https://gravatar.com/avatar/test", + onDismissRequest = {} + ) + } + } +}