diff --git a/app/src/main/kotlin/dev/meloda/fast/presentation/RootScreen.kt b/app/src/main/kotlin/dev/meloda/fast/presentation/RootScreen.kt index f43754df..a55bf359 100644 --- a/app/src/main/kotlin/dev/meloda/fast/presentation/RootScreen.kt +++ b/app/src/main/kotlin/dev/meloda/fast/presentation/RootScreen.kt @@ -7,6 +7,8 @@ import androidx.activity.compose.LocalActivity import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn 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.Text import androidx.compose.material3.TextButton @@ -14,6 +16,10 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect 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.res.stringResource 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.navigation.Main import dev.meloda.fast.navigation.mainScreen -import dev.meloda.fast.photoviewer.navigation.navigateToPhotoView -import dev.meloda.fast.photoviewer.navigation.photoViewScreen +import dev.meloda.fast.photoviewer.presentation.PhotoViewDialog import dev.meloda.fast.settings.navigation.navigateToSettings import dev.meloda.fast.settings.navigation.settingsScreen import dev.meloda.fast.ui.R @@ -120,64 +125,75 @@ fun RootScreen( LocalNavRootController provides navController, LocalNavController provides navController ) { - NavHost( - navController = navController, - startDestination = requireNotNull(startDestination), - enterTransition = { fadeIn(animationSpec = tween(200)) }, - exitTransition = { fadeOut(animationSpec = tween(200)) } - ) { - authNavGraph( - onNavigateToMain = { - viewModel.onUserAuthenticated() - navController.navigateToMain() - }, - onNavigateToSettings = navController::navigateToSettings, - navController = navController - ) + var photoViewerInfo by rememberSaveable { + mutableStateOf, Int?>?>(null) + } - mainScreen( - onError = viewModel::onError, - onSettingsButtonClicked = navController::navigateToSettings, - onNavigateToMessagesHistory = navController::navigateToMessagesHistory, - onPhotoClicked = { url -> navController.navigateToPhotoView(listOf(url)) }, - onMessageClicked = navController::navigateToMessagesHistory, - onNavigateToCreateChat = navController::navigateToCreateChat - ) + Box(modifier = Modifier.fillMaxSize()) { + NavHost( + navController = navController, + startDestination = requireNotNull(startDestination), + enterTransition = { fadeIn(animationSpec = tween(200)) }, + exitTransition = { fadeOut(animationSpec = tween(200)) } + ) { + authNavGraph( + onNavigateToMain = { + viewModel.onUserAuthenticated() + navController.navigateToMain() + }, + onNavigateToSettings = navController::navigateToSettings, + navController = navController + ) - messagesHistoryScreen( - onError = viewModel::onError, - onBack = navController::navigateUp, - onNavigateToChatMaterials = navController::navigateToChatMaterials, - onNavigateToPhotoViewer = navController::navigateToPhotoView - ) - chatMaterialsScreen( - onBack = navController::navigateUp, - onPhotoClicked = { url -> navController.navigateToPhotoView(listOf(url)) } - ) - createChatScreen( - onChatCreated = { conversationId -> - navController.popBackStack() - navController.navigateToMessagesHistory(conversationId) - }, - navController = navController - ) + mainScreen( + onError = viewModel::onError, + onSettingsButtonClicked = navController::navigateToSettings, + onNavigateToMessagesHistory = navController::navigateToMessagesHistory, + onPhotoClicked = { url -> photoViewerInfo = listOf(url) to null }, + onMessageClicked = navController::navigateToMessagesHistory, + onNavigateToCreateChat = navController::navigateToCreateChat + ) - settingsScreen( - onBack = navController::navigateUp, - onLogOutButtonClicked = { navController.navigateToAuth(true) }, - onLanguageItemClicked = navController::navigateToLanguagePicker, - onRestartRequired = { - activity?.let { - val intent = Intent(activity, MainActivity::class.java) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) - activity.startActivity(intent) - activity.finish() + messagesHistoryScreen( + onError = viewModel::onError, + onBack = navController::navigateUp, + onNavigateToChatMaterials = navController::navigateToChatMaterials, + onNavigateToPhotoViewer = { photos, index -> + photoViewerInfo = photos to index } - } - ) - languagePickerScreen(onBack = navController::navigateUp) + ) + chatMaterialsScreen( + onBack = navController::navigateUp, + onPhotoClicked = { url -> photoViewerInfo = listOf(url) to null } + ) + createChatScreen( + onChatCreated = { conversationId -> + navController.popBackStack() + navController.navigateToMessagesHistory(conversationId) + }, + navController = navController + ) - photoViewScreen(onBack = navController::navigateUp) + settingsScreen( + onBack = navController::navigateUp, + onLogOutButtonClicked = { navController.navigateToAuth(true) }, + onLanguageItemClicked = navController::navigateToLanguagePicker, + onRestartRequired = { + activity?.let { + val intent = Intent(activity, MainActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + activity.startActivity(intent) + activity.finish() + } + } + ) + languagePickerScreen(onBack = navController::navigateUp) + } + + PhotoViewDialog( + photoViewerInfo = photoViewerInfo, + onDismiss = { photoViewerInfo = null } + ) } } } diff --git a/core/model/src/main/kotlin/dev/meloda/fast/model/api/data/VkUserData.kt b/core/model/src/main/kotlin/dev/meloda/fast/model/api/data/VkUserData.kt index d59b4439..e31c9b7c 100644 --- a/core/model/src/main/kotlin/dev/meloda/fast/model/api/data/VkUserData.kt +++ b/core/model/src/main/kotlin/dev/meloda/fast/model/api/data/VkUserData.kt @@ -37,7 +37,7 @@ data class VkUserData( @JsonClass(generateAdapter = true) data class LastSeen( - @Json(name = "platform") val platform: Int, + @Json(name = "platform") val platform: Int?, @Json(name = "time") val time: Int ) diff --git a/core/ui/src/main/kotlin/dev/meloda/fast/ui/components/FullScreenDialog.kt b/core/ui/src/main/kotlin/dev/meloda/fast/ui/components/FullScreenDialog.kt new file mode 100644 index 00000000..12ae5563 --- /dev/null +++ b/core/ui/src/main/kotlin/dev/meloda/fast/ui/components/FullScreenDialog.kt @@ -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() + } + } +} diff --git a/core/ui/src/main/kotlin/dev/meloda/fast/ui/components/Loaders.kt b/core/ui/src/main/kotlin/dev/meloda/fast/ui/components/Loaders.kt index c79724b5..4ef01c2f 100644 --- a/core/ui/src/main/kotlin/dev/meloda/fast/ui/components/Loaders.kt +++ b/core/ui/src/main/kotlin/dev/meloda/fast/ui/components/Loaders.kt @@ -10,52 +10,70 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview @Composable @Preview -fun FullScreenContainedLoader(modifier: Modifier = Modifier) { +fun FullScreenContainedLoader( + modifier: Modifier = Modifier, + containerColor: Color = MaterialTheme.colorScheme.primary, + indicatorColor: Color = MaterialTheme.colorScheme.primaryContainer +) { Box( modifier = modifier .fillMaxSize() .navigationBarsPadding(), contentAlignment = Alignment.Center ) { - ContainedLoader() + ContainedLoader( + containerColor = containerColor, + indicatorColor = indicatorColor + ) } } @Preview @Composable -fun FullScreenLoader(modifier: Modifier = Modifier) { +fun FullScreenLoader( + modifier: Modifier = Modifier, + color: Color = MaterialTheme.colorScheme.primary +) { Box( modifier = modifier .fillMaxSize() .navigationBarsPadding(), contentAlignment = Alignment.Center ) { - Loader() + Loader(color = color) } } @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable @Preview -fun ContainedLoader(modifier: Modifier = Modifier) { +fun ContainedLoader( + modifier: Modifier = Modifier, + containerColor: Color = MaterialTheme.colorScheme.primary, + indicatorColor: Color = MaterialTheme.colorScheme.primaryContainer +) { ContainedLoadingIndicator( modifier = modifier, - containerColor = MaterialTheme.colorScheme.primary, - indicatorColor = MaterialTheme.colorScheme.primaryContainer + containerColor = containerColor, + indicatorColor = indicatorColor ) } @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable @Preview -fun Loader(modifier: Modifier = Modifier) { +fun Loader( + modifier: Modifier = Modifier, + color: Color = MaterialTheme.colorScheme.primary +) { LoadingIndicator( modifier = modifier, - color = MaterialTheme.colorScheme.primary + color = color ) } diff --git a/feature/photoviewer/src/main/kotlin/dev/meloda/fast/photoviewer/PhotoViewViewModel.kt b/feature/photoviewer/src/main/kotlin/dev/meloda/fast/photoviewer/PhotoViewViewModel.kt index ecb6b005..62c712d5 100644 --- a/feature/photoviewer/src/main/kotlin/dev/meloda/fast/photoviewer/PhotoViewViewModel.kt +++ b/feature/photoviewer/src/main/kotlin/dev/meloda/fast/photoviewer/PhotoViewViewModel.kt @@ -17,6 +17,7 @@ import coil.imageLoader import coil.request.ImageRequest import dev.meloda.fast.common.extensions.setValue 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.navigation.PhotoView import kotlinx.coroutines.Dispatchers @@ -47,17 +48,22 @@ interface PhotoViewViewModel { } class PhotoViewViewModelImpl( - savedStateHandle: SavedStateHandle, + arguments: PhotoViewArguments, private val applicationContext: Context ) : 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(null) init { - val arguments = PhotoView.from(savedStateHandle).arguments - screenState.setValue { old -> old.copy( images = arguments.imageUrls @@ -165,20 +171,37 @@ class PhotoViewViewModelImpl( } private suspend fun downloadAndStoreImageToCache(url: String): File? = - withContext(Dispatchers.IO) { - val drawable = applicationContext.imageLoader.execute( - ImageRequest.Builder(applicationContext) - .data(url) - .build() - ).drawable ?: return@withContext null + runCatching { + withContext(Dispatchers.IO) { + screenState.setValue { old -> old.copy(isLoading = true) } - val imagesDir = File(applicationContext.cacheDir, "images") - if (!imagesDir.exists()) imagesDir.mkdirs() - val imageFile = File(imagesDir, "shared_image_id${UUID.randomUUID()}.png") - FileOutputStream(imageFile).use { - drawable.toBitmapOrNull()?.compress(Bitmap.CompressFormat.PNG, 100, it) + val drawable = applicationContext.imageLoader.execute( + ImageRequest.Builder(applicationContext) + .data(url) + .build() + ).drawable ?: run { + screenState.setValue { old -> old.copy(isLoading = false) } + return@withContext null + } + + val imagesDir = File(applicationContext.cacheDir, "images") + if (!imagesDir.exists()) imagesDir.mkdirs() + val imageFile = File(imagesDir, "shared_image_id${UUID.randomUUID()}.png") + FileOutputStream(imageFile).use { + drawable.toBitmapOrNull()?.compress(Bitmap.CompressFormat.PNG, 100, it) + } + + 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 + } + ) } diff --git a/feature/photoviewer/src/main/kotlin/dev/meloda/fast/photoviewer/di/PhotoViewDI.kt b/feature/photoviewer/src/main/kotlin/dev/meloda/fast/photoviewer/di/PhotoViewDI.kt index 4bbaca21..99f912da 100644 --- a/feature/photoviewer/src/main/kotlin/dev/meloda/fast/photoviewer/di/PhotoViewDI.kt +++ b/feature/photoviewer/src/main/kotlin/dev/meloda/fast/photoviewer/di/PhotoViewDI.kt @@ -2,10 +2,15 @@ package dev.meloda.fast.photoviewer.di import dev.meloda.fast.photoviewer.PhotoViewViewModel 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.module val photoViewModule = module { - viewModelOf(::PhotoViewViewModelImpl) bind PhotoViewViewModel::class + viewModel { + PhotoViewViewModelImpl( + savedStateHandle = get(), + applicationContext = get() + ) + } bind PhotoViewViewModel::class } diff --git a/feature/photoviewer/src/main/kotlin/dev/meloda/fast/photoviewer/model/PhotoViewScreenState.kt b/feature/photoviewer/src/main/kotlin/dev/meloda/fast/photoviewer/model/PhotoViewScreenState.kt index 9511987b..f7183a1b 100644 --- a/feature/photoviewer/src/main/kotlin/dev/meloda/fast/photoviewer/model/PhotoViewScreenState.kt +++ b/feature/photoviewer/src/main/kotlin/dev/meloda/fast/photoviewer/model/PhotoViewScreenState.kt @@ -6,13 +6,15 @@ import dev.meloda.fast.common.model.UiImage @Immutable data class PhotoViewScreenState( val images: List, - val selectedPage: Int + val selectedPage: Int, + val isLoading: Boolean ) { companion object { val EMPTY: PhotoViewScreenState = PhotoViewScreenState( images = emptyList(), - selectedPage = 0 + selectedPage = 0, + isLoading = false ) } } diff --git a/feature/photoviewer/src/main/kotlin/dev/meloda/fast/photoviewer/navigation/PhotoViewNavigation.kt b/feature/photoviewer/src/main/kotlin/dev/meloda/fast/photoviewer/navigation/PhotoViewNavigation.kt index b400ae5c..cdc93525 100644 --- a/feature/photoviewer/src/main/kotlin/dev/meloda/fast/photoviewer/navigation/PhotoViewNavigation.kt +++ b/feature/photoviewer/src/main/kotlin/dev/meloda/fast/photoviewer/navigation/PhotoViewNavigation.kt @@ -1,15 +1,10 @@ package dev.meloda.fast.photoviewer.navigation import androidx.lifecycle.SavedStateHandle -import androidx.navigation.NavController -import androidx.navigation.NavGraphBuilder -import androidx.navigation.compose.composable import androidx.navigation.toRoute import dev.meloda.fast.photoviewer.model.PhotoViewArguments -import dev.meloda.fast.photoviewer.presentation.PhotoViewRoute import dev.meloda.fast.ui.extensions.customNavType import kotlinx.serialization.Serializable -import java.net.URLEncoder import kotlin.reflect.typeOf @Serializable @@ -21,25 +16,3 @@ data class PhotoView(val arguments: PhotoViewArguments) { savedStateHandle.toRoute(typeMap) } } - -fun NavGraphBuilder.photoViewScreen( - onBack: () -> Unit -) { - composable(typeMap = PhotoView.typeMap) { - PhotoViewRoute(onBack = onBack) - } -} - -fun NavController.navigateToPhotoView( - images: List, - selectedIndex: Int? = null -) { - this.navigate( - PhotoView( - arguments = PhotoViewArguments( - imageUrls = images.map { URLEncoder.encode(it, "utf-8") }, - selectedIndex = selectedIndex - ) - ) - ) -} diff --git a/feature/photoviewer/src/main/kotlin/dev/meloda/fast/photoviewer/presentation/PhotoViewScreen.kt b/feature/photoviewer/src/main/kotlin/dev/meloda/fast/photoviewer/presentation/PhotoViewScreen.kt index 5f673d91..17681955 100644 --- a/feature/photoviewer/src/main/kotlin/dev/meloda/fast/photoviewer/presentation/PhotoViewScreen.kt +++ b/feature/photoviewer/src/main/kotlin/dev/meloda/fast/photoviewer/presentation/PhotoViewScreen.kt @@ -2,8 +2,13 @@ package dev.meloda.fast.photoviewer.presentation import android.content.Intent 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.tween 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.draggable import androidx.compose.foundation.gestures.rememberDraggableState @@ -24,7 +29,6 @@ import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar @@ -40,32 +44,65 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.painter.ColorPainter import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalWindowInfo import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle 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.PhotoViewViewModelImpl +import dev.meloda.fast.photoviewer.model.PhotoViewArguments 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 kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.koin.androidx.compose.koinViewModel +import java.net.URLEncoder import kotlin.math.abs import dev.meloda.fast.ui.R as UiR @Composable -fun PhotoViewRoute( +fun PhotoViewDialog( + photoViewerInfo: Pair, 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, viewModel: PhotoViewViewModel = koinViewModel() ) { @@ -80,7 +117,7 @@ fun PhotoViewRoute( viewModel.onImageShared() val intent = Intent(Intent.ACTION_SEND).apply { - setType("image/png") + type = "image/png" addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) putExtra(Intent.EXTRA_STREAM, shareRequest) } @@ -116,7 +153,7 @@ fun PhotoViewRoute( } @Composable -fun PhotoViewScreen( +private fun PhotoViewScreen( screenState: PhotoViewScreenState = PhotoViewScreenState.EMPTY, onBack: () -> Unit = {}, onPageChanged: (index: Int) -> Unit = {}, @@ -148,7 +185,6 @@ fun PhotoViewScreen( } Scaffold( - modifier = Modifier.graphicsLayer(alpha = calculatedAlpha), topBar = { TopBar( onBack = onBack, @@ -158,9 +194,7 @@ fun PhotoViewScreen( onCopyLinkClicked = onCopyLinkClicked, ) }, - containerColor = MaterialTheme.colorScheme.background.copy( - alpha = calculatedAlpha - ) + containerColor = Color.Black.copy(alpha = calculatedAlpha) ) { padding -> Box(modifier = Modifier.fillMaxSize()) { Pager( @@ -169,15 +203,34 @@ fun PhotoViewScreen( padding = padding, onBack = onBack, 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) @Composable -fun TopBar( +private fun TopBar( modifier: Modifier = Modifier, onBack: () -> Unit, onShareClicked: () -> Unit, @@ -193,12 +246,16 @@ fun TopBar( TopAppBar( modifier = modifier, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = Color.Black.copy(alpha = 0.3f) + ), title = {}, navigationIcon = { IconButton(onClick = onBack) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, - contentDescription = "Back button" + contentDescription = "Back button", + tint = Color.White ) } }, @@ -208,7 +265,8 @@ fun TopBar( ) { Icon( imageVector = Icons.Rounded.MoreVert, - contentDescription = "Options" + contentDescription = "Options", + tint = Color.White ) } @@ -255,13 +313,12 @@ fun TopBar( }, ) } - }, - colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent) + } ) } @Composable -fun Pager( +private fun Pager( modifier: Modifier = Modifier, pagerState: PagerState, state: PhotoViewScreenState, @@ -269,6 +326,8 @@ fun Pager( onBack: () -> Unit, onVerticalDrag: (offset: Float) -> Unit ) { + val windowInfo = LocalWindowInfo.current + HorizontalPager( state = pagerState, modifier = modifier.fillMaxSize() @@ -289,22 +348,25 @@ fun Pager( } else { var offsetY by remember { mutableFloatStateOf(0f) } - val animatedOffset by animateFloatAsState( - targetValue = offsetY, - label = "animatedOffset" - ) var useAnimatedOffset by remember { mutableStateOf(false) } + val animatedOffset by animateFloatAsState( + targetValue = offsetY, + label = "animatedOffset", + animationSpec = tween( + durationMillis = if (useAnimatedOffset) 150 else 0, + easing = LinearEasing + ) + ) + AsyncImage( model = model, contentDescription = "Image", modifier = Modifier .graphicsLayer { - this.translationY = if (useAnimatedOffset) { - animatedOffset - } else offsetY + this.translationY = animatedOffset } .draggable( state = rememberDraggableState { delta -> @@ -314,7 +376,7 @@ fun Pager( }, orientation = Orientation.Vertical, onDragStopped = { - if (abs(offsetY.pxToDp()) >= 200) { + if (abs(offsetY) / windowInfo.containerSize.height >= 0.25) { onBack() } else { 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 - ) - ) -}