Refactor: Use Dialog for PhotoViewScreen

This commit refactors the PhotoViewScreen to be displayed as a Dialog instead of a separate navigation destination.

Key changes:
- Introduced `PhotoViewDialog` composable that wraps `PhotoViewRoute` in a `FullScreenDialog`.
- Modified `RootScreen` to use `PhotoViewDialog` for displaying images.
- Updated `PhotoViewViewModelImpl` to handle loading state and display a loader while downloading images.
- Made `Loader` and `ContainedLoader` colors configurable.
- Adjusted `PhotoViewScreen` UI:
    - Set background to translucent black.
    - Updated TopAppBar background color and icon tints.
    - Improved vertical drag gesture for dismissing the viewer.
- Made `VkUserData.LastSeen.platform` nullable.
- Removed unused navigation functions related to the old PhotoViewScreen.
This commit is contained in:
2025-08-19 22:54:38 +03:00
parent 7e25bc3a8d
commit 252f6ec21e
9 changed files with 272 additions and 153 deletions
@@ -7,6 +7,8 @@ import androidx.activity.compose.LocalActivity
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
@@ -14,6 +16,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
@@ -35,8 +41,7 @@ import dev.meloda.fast.messageshistory.navigation.messagesHistoryScreen
import dev.meloda.fast.messageshistory.navigation.navigateToMessagesHistory import dev.meloda.fast.messageshistory.navigation.navigateToMessagesHistory
import dev.meloda.fast.navigation.Main import dev.meloda.fast.navigation.Main
import dev.meloda.fast.navigation.mainScreen import dev.meloda.fast.navigation.mainScreen
import dev.meloda.fast.photoviewer.navigation.navigateToPhotoView import dev.meloda.fast.photoviewer.presentation.PhotoViewDialog
import dev.meloda.fast.photoviewer.navigation.photoViewScreen
import dev.meloda.fast.settings.navigation.navigateToSettings import dev.meloda.fast.settings.navigation.navigateToSettings
import dev.meloda.fast.settings.navigation.settingsScreen import dev.meloda.fast.settings.navigation.settingsScreen
import dev.meloda.fast.ui.R import dev.meloda.fast.ui.R
@@ -120,6 +125,11 @@ fun RootScreen(
LocalNavRootController provides navController, LocalNavRootController provides navController,
LocalNavController provides navController LocalNavController provides navController
) { ) {
var photoViewerInfo by rememberSaveable {
mutableStateOf<Pair<List<String>, Int?>?>(null)
}
Box(modifier = Modifier.fillMaxSize()) {
NavHost( NavHost(
navController = navController, navController = navController,
startDestination = requireNotNull(startDestination), startDestination = requireNotNull(startDestination),
@@ -139,7 +149,7 @@ fun RootScreen(
onError = viewModel::onError, onError = viewModel::onError,
onSettingsButtonClicked = navController::navigateToSettings, onSettingsButtonClicked = navController::navigateToSettings,
onNavigateToMessagesHistory = navController::navigateToMessagesHistory, onNavigateToMessagesHistory = navController::navigateToMessagesHistory,
onPhotoClicked = { url -> navController.navigateToPhotoView(listOf(url)) }, onPhotoClicked = { url -> photoViewerInfo = listOf(url) to null },
onMessageClicked = navController::navigateToMessagesHistory, onMessageClicked = navController::navigateToMessagesHistory,
onNavigateToCreateChat = navController::navigateToCreateChat onNavigateToCreateChat = navController::navigateToCreateChat
) )
@@ -148,11 +158,13 @@ fun RootScreen(
onError = viewModel::onError, onError = viewModel::onError,
onBack = navController::navigateUp, onBack = navController::navigateUp,
onNavigateToChatMaterials = navController::navigateToChatMaterials, onNavigateToChatMaterials = navController::navigateToChatMaterials,
onNavigateToPhotoViewer = navController::navigateToPhotoView onNavigateToPhotoViewer = { photos, index ->
photoViewerInfo = photos to index
}
) )
chatMaterialsScreen( chatMaterialsScreen(
onBack = navController::navigateUp, onBack = navController::navigateUp,
onPhotoClicked = { url -> navController.navigateToPhotoView(listOf(url)) } onPhotoClicked = { url -> photoViewerInfo = listOf(url) to null }
) )
createChatScreen( createChatScreen(
onChatCreated = { conversationId -> onChatCreated = { conversationId ->
@@ -176,8 +188,12 @@ fun RootScreen(
} }
) )
languagePickerScreen(onBack = navController::navigateUp) languagePickerScreen(onBack = navController::navigateUp)
}
photoViewScreen(onBack = navController::navigateUp) PhotoViewDialog(
photoViewerInfo = photoViewerInfo,
onDismiss = { photoViewerInfo = null }
)
} }
} }
} }
@@ -37,7 +37,7 @@ data class VkUserData(
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class LastSeen( data class LastSeen(
@Json(name = "platform") val platform: Int, @Json(name = "platform") val platform: Int?,
@Json(name = "time") val time: Int @Json(name = "time") val time: Int
) )
@@ -0,0 +1,33 @@
package dev.meloda.fast.ui.components
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.zIndex
@Composable
fun FullScreenDialog(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Dialog(
onDismissRequest = {},
DialogProperties(
usePlatformDefaultWidth = false,
decorFitsSystemWindows = false
)
) {
Box(
modifier = modifier
.fillMaxSize()
.zIndex(10F),
contentAlignment = Alignment.Center
) {
content()
}
}
}
@@ -10,52 +10,70 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
@Composable @Composable
@Preview @Preview
fun FullScreenContainedLoader(modifier: Modifier = Modifier) { fun FullScreenContainedLoader(
modifier: Modifier = Modifier,
containerColor: Color = MaterialTheme.colorScheme.primary,
indicatorColor: Color = MaterialTheme.colorScheme.primaryContainer
) {
Box( Box(
modifier = modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
.navigationBarsPadding(), .navigationBarsPadding(),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
ContainedLoader() ContainedLoader(
containerColor = containerColor,
indicatorColor = indicatorColor
)
} }
} }
@Preview @Preview
@Composable @Composable
fun FullScreenLoader(modifier: Modifier = Modifier) { fun FullScreenLoader(
modifier: Modifier = Modifier,
color: Color = MaterialTheme.colorScheme.primary
) {
Box( Box(
modifier = modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
.navigationBarsPadding(), .navigationBarsPadding(),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Loader() Loader(color = color)
} }
} }
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
@Preview @Preview
fun ContainedLoader(modifier: Modifier = Modifier) { fun ContainedLoader(
modifier: Modifier = Modifier,
containerColor: Color = MaterialTheme.colorScheme.primary,
indicatorColor: Color = MaterialTheme.colorScheme.primaryContainer
) {
ContainedLoadingIndicator( ContainedLoadingIndicator(
modifier = modifier, modifier = modifier,
containerColor = MaterialTheme.colorScheme.primary, containerColor = containerColor,
indicatorColor = MaterialTheme.colorScheme.primaryContainer indicatorColor = indicatorColor
) )
} }
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
@Preview @Preview
fun Loader(modifier: Modifier = Modifier) { fun Loader(
modifier: Modifier = Modifier,
color: Color = MaterialTheme.colorScheme.primary
) {
LoadingIndicator( LoadingIndicator(
modifier = modifier, modifier = modifier,
color = MaterialTheme.colorScheme.primary color = color
) )
} }
@@ -17,6 +17,7 @@ import coil.imageLoader
import coil.request.ImageRequest import coil.request.ImageRequest
import dev.meloda.fast.common.extensions.setValue import dev.meloda.fast.common.extensions.setValue
import dev.meloda.fast.common.model.UiImage import dev.meloda.fast.common.model.UiImage
import dev.meloda.fast.photoviewer.model.PhotoViewArguments
import dev.meloda.fast.photoviewer.model.PhotoViewScreenState import dev.meloda.fast.photoviewer.model.PhotoViewScreenState
import dev.meloda.fast.photoviewer.navigation.PhotoView import dev.meloda.fast.photoviewer.navigation.PhotoView
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -47,17 +48,22 @@ interface PhotoViewViewModel {
} }
class PhotoViewViewModelImpl( class PhotoViewViewModelImpl(
savedStateHandle: SavedStateHandle, arguments: PhotoViewArguments,
private val applicationContext: Context private val applicationContext: Context
) : PhotoViewViewModel, ViewModel() { ) : PhotoViewViewModel, ViewModel() {
override val screenState = MutableStateFlow(PhotoViewScreenState.EMPTY) constructor(
savedStateHandle: SavedStateHandle,
applicationContext: Context
) : this(
arguments = PhotoView.from(savedStateHandle).arguments,
applicationContext = applicationContext
)
override val screenState = MutableStateFlow(PhotoViewScreenState.EMPTY)
override val shareRequest = MutableStateFlow<Uri?>(null) override val shareRequest = MutableStateFlow<Uri?>(null)
init { init {
val arguments = PhotoView.from(savedStateHandle).arguments
screenState.setValue { old -> screenState.setValue { old ->
old.copy( old.copy(
images = arguments.imageUrls images = arguments.imageUrls
@@ -165,12 +171,18 @@ class PhotoViewViewModelImpl(
} }
private suspend fun downloadAndStoreImageToCache(url: String): File? = private suspend fun downloadAndStoreImageToCache(url: String): File? =
runCatching {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
screenState.setValue { old -> old.copy(isLoading = true) }
val drawable = applicationContext.imageLoader.execute( val drawable = applicationContext.imageLoader.execute(
ImageRequest.Builder(applicationContext) ImageRequest.Builder(applicationContext)
.data(url) .data(url)
.build() .build()
).drawable ?: return@withContext null ).drawable ?: run {
screenState.setValue { old -> old.copy(isLoading = false) }
return@withContext null
}
val imagesDir = File(applicationContext.cacheDir, "images") val imagesDir = File(applicationContext.cacheDir, "images")
if (!imagesDir.exists()) imagesDir.mkdirs() if (!imagesDir.exists()) imagesDir.mkdirs()
@@ -181,4 +193,15 @@ class PhotoViewViewModelImpl(
imageFile imageFile
} }
}.fold(
onSuccess = { file ->
screenState.setValue { old -> old.copy(isLoading = false) }
file
},
onFailure = { e ->
e.printStackTrace()
screenState.setValue { old -> old.copy(isLoading = false) }
null
}
)
} }
@@ -2,10 +2,15 @@ package dev.meloda.fast.photoviewer.di
import dev.meloda.fast.photoviewer.PhotoViewViewModel import dev.meloda.fast.photoviewer.PhotoViewViewModel
import dev.meloda.fast.photoviewer.PhotoViewViewModelImpl import dev.meloda.fast.photoviewer.PhotoViewViewModelImpl
import org.koin.core.module.dsl.viewModelOf import org.koin.core.module.dsl.viewModel
import org.koin.dsl.bind import org.koin.dsl.bind
import org.koin.dsl.module import org.koin.dsl.module
val photoViewModule = module { val photoViewModule = module {
viewModelOf(::PhotoViewViewModelImpl) bind PhotoViewViewModel::class viewModel {
PhotoViewViewModelImpl(
savedStateHandle = get(),
applicationContext = get()
)
} bind PhotoViewViewModel::class
} }
@@ -6,13 +6,15 @@ import dev.meloda.fast.common.model.UiImage
@Immutable @Immutable
data class PhotoViewScreenState( data class PhotoViewScreenState(
val images: List<UiImage>, val images: List<UiImage>,
val selectedPage: Int val selectedPage: Int,
val isLoading: Boolean
) { ) {
companion object { companion object {
val EMPTY: PhotoViewScreenState = PhotoViewScreenState( val EMPTY: PhotoViewScreenState = PhotoViewScreenState(
images = emptyList(), images = emptyList(),
selectedPage = 0 selectedPage = 0,
isLoading = false
) )
} }
} }
@@ -1,15 +1,10 @@
package dev.meloda.fast.photoviewer.navigation package dev.meloda.fast.photoviewer.navigation
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import androidx.navigation.toRoute import androidx.navigation.toRoute
import dev.meloda.fast.photoviewer.model.PhotoViewArguments import dev.meloda.fast.photoviewer.model.PhotoViewArguments
import dev.meloda.fast.photoviewer.presentation.PhotoViewRoute
import dev.meloda.fast.ui.extensions.customNavType import dev.meloda.fast.ui.extensions.customNavType
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import java.net.URLEncoder
import kotlin.reflect.typeOf import kotlin.reflect.typeOf
@Serializable @Serializable
@@ -21,25 +16,3 @@ data class PhotoView(val arguments: PhotoViewArguments) {
savedStateHandle.toRoute<PhotoView>(typeMap) savedStateHandle.toRoute<PhotoView>(typeMap)
} }
} }
fun NavGraphBuilder.photoViewScreen(
onBack: () -> Unit
) {
composable<PhotoView>(typeMap = PhotoView.typeMap) {
PhotoViewRoute(onBack = onBack)
}
}
fun NavController.navigateToPhotoView(
images: List<String>,
selectedIndex: Int? = null
) {
this.navigate(
PhotoView(
arguments = PhotoViewArguments(
imageUrls = images.map { URLEncoder.encode(it, "utf-8") },
selectedIndex = selectedIndex
)
)
)
}
@@ -2,8 +2,13 @@ package dev.meloda.fast.photoviewer.presentation
import android.content.Intent import android.content.Intent
import android.widget.Toast import android.widget.Toast
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.draggable import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.gestures.rememberDraggableState import androidx.compose.foundation.gestures.rememberDraggableState
@@ -24,7 +29,6 @@ import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
@@ -40,32 +44,65 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.painter.ColorPainter import androidx.compose.ui.graphics.painter.ColorPainter
import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalWindowInfo
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.conena.nanokt.android.content.pxToDp
import dev.meloda.fast.common.model.UiImage
import dev.meloda.fast.photoviewer.PhotoViewViewModel import dev.meloda.fast.photoviewer.PhotoViewViewModel
import dev.meloda.fast.photoviewer.PhotoViewViewModelImpl import dev.meloda.fast.photoviewer.PhotoViewViewModelImpl
import dev.meloda.fast.photoviewer.model.PhotoViewArguments
import dev.meloda.fast.photoviewer.model.PhotoViewScreenState import dev.meloda.fast.photoviewer.model.PhotoViewScreenState
import dev.meloda.fast.ui.components.FullScreenDialog
import dev.meloda.fast.ui.components.Loader
import dev.meloda.fast.ui.util.getImage import dev.meloda.fast.ui.util.getImage
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
import java.net.URLEncoder
import kotlin.math.abs import kotlin.math.abs
import dev.meloda.fast.ui.R as UiR import dev.meloda.fast.ui.R as UiR
@Composable @Composable
fun PhotoViewRoute( fun PhotoViewDialog(
photoViewerInfo: Pair<List<String>, Int?>?,
modifier: Modifier = Modifier,
onDismiss: () -> Unit
) {
val applicationContext = LocalContext.current.applicationContext
if (photoViewerInfo != null) {
FullScreenDialog(modifier = modifier) {
val viewModel = remember(true) {
PhotoViewViewModelImpl(
arguments = PhotoViewArguments(
imageUrls = photoViewerInfo.first.map {
URLEncoder.encode(it, "utf-8")
},
selectedIndex = photoViewerInfo.second
),
applicationContext = applicationContext
)
}
PhotoViewRoute(
onBack = onDismiss,
viewModel = viewModel
)
}
}
}
@Composable
private fun PhotoViewRoute(
onBack: () -> Unit, onBack: () -> Unit,
viewModel: PhotoViewViewModel = koinViewModel<PhotoViewViewModelImpl>() viewModel: PhotoViewViewModel = koinViewModel<PhotoViewViewModelImpl>()
) { ) {
@@ -80,7 +117,7 @@ fun PhotoViewRoute(
viewModel.onImageShared() viewModel.onImageShared()
val intent = Intent(Intent.ACTION_SEND).apply { val intent = Intent(Intent.ACTION_SEND).apply {
setType("image/png") type = "image/png"
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
putExtra(Intent.EXTRA_STREAM, shareRequest) putExtra(Intent.EXTRA_STREAM, shareRequest)
} }
@@ -116,7 +153,7 @@ fun PhotoViewRoute(
} }
@Composable @Composable
fun PhotoViewScreen( private fun PhotoViewScreen(
screenState: PhotoViewScreenState = PhotoViewScreenState.EMPTY, screenState: PhotoViewScreenState = PhotoViewScreenState.EMPTY,
onBack: () -> Unit = {}, onBack: () -> Unit = {},
onPageChanged: (index: Int) -> Unit = {}, onPageChanged: (index: Int) -> Unit = {},
@@ -148,7 +185,6 @@ fun PhotoViewScreen(
} }
Scaffold( Scaffold(
modifier = Modifier.graphicsLayer(alpha = calculatedAlpha),
topBar = { topBar = {
TopBar( TopBar(
onBack = onBack, onBack = onBack,
@@ -158,9 +194,7 @@ fun PhotoViewScreen(
onCopyLinkClicked = onCopyLinkClicked, onCopyLinkClicked = onCopyLinkClicked,
) )
}, },
containerColor = MaterialTheme.colorScheme.background.copy( containerColor = Color.Black.copy(alpha = calculatedAlpha)
alpha = calculatedAlpha
)
) { padding -> ) { padding ->
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize()) {
Pager( Pager(
@@ -169,15 +203,34 @@ fun PhotoViewScreen(
padding = padding, padding = padding,
onBack = onBack, onBack = onBack,
onVerticalDrag = { offset -> offsetY = offset }, onVerticalDrag = { offset -> offsetY = offset },
modifier = Modifier
) )
AnimatedVisibility(
visible = screenState.isLoading,
modifier = Modifier
.fillMaxSize()
.clickable(
interactionSource = null,
indication = null,
onClick = {}
)
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.5f)),
contentAlignment = Alignment.Center
) {
Loader(color = Color.White)
}
}
} }
} }
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun TopBar( private fun TopBar(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onBack: () -> Unit, onBack: () -> Unit,
onShareClicked: () -> Unit, onShareClicked: () -> Unit,
@@ -193,12 +246,16 @@ fun TopBar(
TopAppBar( TopAppBar(
modifier = modifier, modifier = modifier,
colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Black.copy(alpha = 0.3f)
),
title = {}, title = {},
navigationIcon = { navigationIcon = {
IconButton(onClick = onBack) { IconButton(onClick = onBack) {
Icon( Icon(
imageVector = Icons.AutoMirrored.Rounded.ArrowBack, imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
contentDescription = "Back button" contentDescription = "Back button",
tint = Color.White
) )
} }
}, },
@@ -208,7 +265,8 @@ fun TopBar(
) { ) {
Icon( Icon(
imageVector = Icons.Rounded.MoreVert, imageVector = Icons.Rounded.MoreVert,
contentDescription = "Options" contentDescription = "Options",
tint = Color.White
) )
} }
@@ -255,13 +313,12 @@ fun TopBar(
}, },
) )
} }
}, }
colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent)
) )
} }
@Composable @Composable
fun Pager( private fun Pager(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
pagerState: PagerState, pagerState: PagerState,
state: PhotoViewScreenState, state: PhotoViewScreenState,
@@ -269,6 +326,8 @@ fun Pager(
onBack: () -> Unit, onBack: () -> Unit,
onVerticalDrag: (offset: Float) -> Unit onVerticalDrag: (offset: Float) -> Unit
) { ) {
val windowInfo = LocalWindowInfo.current
HorizontalPager( HorizontalPager(
state = pagerState, state = pagerState,
modifier = modifier.fillMaxSize() modifier = modifier.fillMaxSize()
@@ -289,22 +348,25 @@ fun Pager(
} else { } else {
var offsetY by remember { mutableFloatStateOf(0f) } var offsetY by remember { mutableFloatStateOf(0f) }
val animatedOffset by animateFloatAsState(
targetValue = offsetY,
label = "animatedOffset"
)
var useAnimatedOffset by remember { var useAnimatedOffset by remember {
mutableStateOf(false) mutableStateOf(false)
} }
val animatedOffset by animateFloatAsState(
targetValue = offsetY,
label = "animatedOffset",
animationSpec = tween(
durationMillis = if (useAnimatedOffset) 150 else 0,
easing = LinearEasing
)
)
AsyncImage( AsyncImage(
model = model, model = model,
contentDescription = "Image", contentDescription = "Image",
modifier = Modifier modifier = Modifier
.graphicsLayer { .graphicsLayer {
this.translationY = if (useAnimatedOffset) { this.translationY = animatedOffset
animatedOffset
} else offsetY
} }
.draggable( .draggable(
state = rememberDraggableState { delta -> state = rememberDraggableState { delta ->
@@ -314,7 +376,7 @@ fun Pager(
}, },
orientation = Orientation.Vertical, orientation = Orientation.Vertical,
onDragStopped = { onDragStopped = {
if (abs(offsetY.pxToDp()) >= 200) { if (abs(offsetY) / windowInfo.containerSize.height >= 0.25) {
onBack() onBack()
} else { } else {
useAnimatedOffset = true useAnimatedOffset = true
@@ -331,16 +393,3 @@ fun Pager(
} }
} }
} }
@Preview
@Composable
private fun PhotoViewScreenPreview() {
PhotoViewScreen(
screenState = PhotoViewScreenState(
images = List(200) {
UiImage.Resource(UiR.drawable.test_captcha)
},
selectedPage = 0
)
)
}