Skip to content
This repository was archived by the owner on Aug 21, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import kotlinx.serialization.Serializable
internal fun HomeNavigation(
navController: NavHostController,
snackbarHostState: SnackbarHostState,
onShouldShowBottomBar: (Boolean) -> Unit,
) {
NavHost(
navController = navController,
Expand All @@ -37,7 +38,8 @@ internal fun HomeNavigation(
composable<HomeDestination.Share> {
ShareScreen(
viewModelStoreOwner = navController.getBackStackEntry(HomeRoute::class),
snackbarHostState = snackbarHostState
snackbarHostState = snackbarHostState,
onShouldShowBottomBar = onShouldShowBottomBar,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.gravatar.app.homeUi.presentation.home

internal sealed class HomeEvent {
data class ShowBottomBar(val show: Boolean) : HomeEvent()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}
}

Expand All @@ -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 ->
Expand Down Expand Up @@ -148,7 +152,7 @@ private fun HomeScreenPreview() {
networkState = null
)
) { navController, snackbarHostState ->
HomeNavigation(navController, snackbarHostState)
HomeNavigation(navController, snackbarHostState) {}
}
}

Expand All @@ -160,6 +164,6 @@ private fun HomeScreenNoInternetPreview() {
networkState = NetworkState.DISCONNECTED
)
) { navController, snackbarHostState ->
HomeNavigation(navController, snackbarHostState)
HomeNavigation(navController, snackbarHostState) {}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -56,6 +63,10 @@ internal fun ShareScreen(
is ShareAction.ShareVCard -> {
shareVCardFile(action.vCardFile, context)
}

is ShareAction.ShowBottomBar -> {
onShouldShowBottomBar(action.show)
}
}
}
}
Expand Down Expand Up @@ -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(),
)
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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 = {}
)
}
}
Loading