Add detail image dialog and change glide to coil.
This commit is contained in:
parent
d6072e9a00
commit
4070a6e5fe
12 changed files with 235 additions and 151 deletions
|
|
@ -142,8 +142,7 @@ dependencies {
|
|||
kapt("com.google.dagger:hilt-compiler:2.48")
|
||||
|
||||
// Image
|
||||
implementation("com.github.skydoves:landscapist-glide:2.1.11")
|
||||
ksp("com.github.bumptech.glide:ksp:4.14.2") // this have to be align with landscapist-glide
|
||||
implementation("io.coil-kt:coil-compose:2.4.0")
|
||||
}
|
||||
|
||||
java {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,31 @@
|
|||
package com.pixelized.rplexicon
|
||||
|
||||
import android.app.Application
|
||||
import coil.ImageLoader
|
||||
import coil.ImageLoaderFactory
|
||||
import coil.disk.DiskCache
|
||||
import coil.memory.MemoryCache
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import java.io.File
|
||||
|
||||
@HiltAndroidApp
|
||||
class MainApplication : Application()
|
||||
class MainApplication : Application(), ImageLoaderFactory {
|
||||
|
||||
override fun newImageLoader(): ImageLoader {
|
||||
return ImageLoader.Builder(this)
|
||||
.memoryCache {
|
||||
MemoryCache.Builder(this)
|
||||
.maxSizePercent(0.25)
|
||||
.build()
|
||||
}
|
||||
.diskCache {
|
||||
DiskCache.Builder()
|
||||
.directory(applicationContext.cacheDir.resolve("image_cache"))
|
||||
.maxSizeBytes(size = 150.Mo)
|
||||
.build()
|
||||
}
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
val Int.Mo: Long get() = (this * 1024 * 1024).toLong()
|
||||
|
|
@ -1,57 +1,38 @@
|
|||
package com.pixelized.rplexicon.ui.composable
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import com.bumptech.glide.request.RequestListener
|
||||
import com.pixelized.rplexicon.utilitary.rememberLoadingTransition
|
||||
import com.skydoves.landscapist.ImageOptions
|
||||
import com.skydoves.landscapist.InternalLandscapistApi
|
||||
import com.skydoves.landscapist.components.ImageComponent
|
||||
import com.skydoves.landscapist.components.rememberImageComponent
|
||||
import com.skydoves.landscapist.glide.GlideImage
|
||||
import com.skydoves.landscapist.glide.GlideImageState
|
||||
import com.skydoves.landscapist.glide.GlideRequestType
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.DefaultAlpha
|
||||
import androidx.compose.ui.graphics.FilterQuality
|
||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import coil.compose.AsyncImagePainter
|
||||
|
||||
@Composable
|
||||
fun AsyncImage(
|
||||
imageModel: () -> Any?,
|
||||
modifier: Modifier = Modifier,
|
||||
glideRequestType: GlideRequestType = GlideRequestType.DRAWABLE,
|
||||
requestListener: (() -> RequestListener<Any>)? = null,
|
||||
component: ImageComponent = rememberImageComponent {},
|
||||
imageOptions: ImageOptions = ImageOptions(),
|
||||
onImageStateChanged: (GlideImageState) -> Unit = {},
|
||||
@DrawableRes previewPlaceholder: Int = 0,
|
||||
loading: @Composable (BoxScope.(imageState: GlideImageState.Loading) -> Unit)? = null,
|
||||
success: @Composable (BoxScope.(imageState: GlideImageState.Success, painter: Painter) -> Unit)? = null,
|
||||
failure: @Composable (BoxScope.(imageState: GlideImageState.Failure) -> Unit)? = null,
|
||||
model: Any?,
|
||||
contentDescription: String? = null,
|
||||
transform: (AsyncImagePainter.State) -> AsyncImagePainter.State = AsyncImagePainter.DefaultTransform,
|
||||
onState: ((AsyncImagePainter.State) -> Unit)? = null,
|
||||
alignment: Alignment = Alignment.Center,
|
||||
contentScale: ContentScale = ContentScale.Fit,
|
||||
alpha: Float = DefaultAlpha,
|
||||
colorFilter: ColorFilter? = null,
|
||||
filterQuality: FilterQuality = DrawScope.DefaultFilterQuality,
|
||||
) {
|
||||
val transition = rememberLoadingTransition(imageModel)
|
||||
|
||||
GlideImage(
|
||||
imageModel = imageModel,
|
||||
modifier = modifier.alpha(alpha = transition.alpha),
|
||||
glideRequestType = glideRequestType,
|
||||
requestListener = requestListener,
|
||||
component = component,
|
||||
imageOptions = imageOptions,
|
||||
onImageStateChanged = {
|
||||
when (it) {
|
||||
is GlideImageState.Success -> transition.target = 1f
|
||||
is GlideImageState.Failure -> transition.target = 1f
|
||||
is GlideImageState.Loading -> transition.target = 0f
|
||||
is GlideImageState.None -> transition.target = 0f
|
||||
}
|
||||
onImageStateChanged(it)
|
||||
},
|
||||
previewPlaceholder = previewPlaceholder,
|
||||
loading = loading,
|
||||
success = success,
|
||||
failure = failure,
|
||||
coil.compose.AsyncImage(
|
||||
modifier = modifier,
|
||||
model = model,
|
||||
contentDescription = contentDescription,
|
||||
alignment = alignment,
|
||||
contentScale = contentScale,
|
||||
alpha = alpha,
|
||||
colorFilter = colorFilter,
|
||||
transform = transform,
|
||||
onState = onState,
|
||||
filterQuality = filterQuality,
|
||||
)
|
||||
}
|
||||
|
|
@ -16,7 +16,6 @@ import androidx.compose.ui.graphics.ColorMatrix
|
|||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.skydoves.landscapist.ImageOptions
|
||||
|
||||
|
||||
@Composable
|
||||
|
|
@ -52,20 +51,17 @@ fun BackgroundImage(
|
|||
ColorMatrix().also { it.setToSaturation(0f) }
|
||||
)
|
||||
},
|
||||
model: () -> Any?,
|
||||
model: Any?,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
) {
|
||||
AsyncImage(
|
||||
modifier = Modifier.matchParentSize(),
|
||||
imageOptions = ImageOptions(
|
||||
alignment = alignment,
|
||||
contentScale = contentScale,
|
||||
colorFilter = colorFilter,
|
||||
),
|
||||
imageModel = model,
|
||||
previewPlaceholder = R.drawable.im_brulkhai,
|
||||
alignment = alignment,
|
||||
contentScale = contentScale,
|
||||
colorFilter = colorFilter,
|
||||
model = model,
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
|
|
|||
|
|
@ -0,0 +1,108 @@
|
|||
package com.pixelized.rplexicon.ui.composable
|
||||
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.uri
|
||||
import com.pixelized.rplexicon.utilitary.extentions.zoomable
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class FullScreenImageViewModel @Inject constructor() : ViewModel() {
|
||||
private val _url = mutableStateOf<Uri?>(null)
|
||||
val url: State<Uri?> get() = _url
|
||||
|
||||
fun showDetail(url: Uri) {
|
||||
_url.value = url
|
||||
}
|
||||
|
||||
fun hideDetail() {
|
||||
_url.value = null
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FullScreenImageHandler(
|
||||
viewModel: FullScreenImageViewModel = hiltViewModel(),
|
||||
) {
|
||||
ImageDialog(
|
||||
url = viewModel.url,
|
||||
onDismissRequest = viewModel::hideDetail,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ImageDialog(
|
||||
url: State<Uri?>,
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
val uri by url
|
||||
if (uri != null) {
|
||||
Dialog(
|
||||
properties = remember {
|
||||
DialogProperties(
|
||||
decorFitsSystemWindows = false,
|
||||
usePlatformDefaultWidth = false,
|
||||
)
|
||||
},
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
Box {
|
||||
AsyncImage(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.zoomable()
|
||||
.padding(all = 16.dp),
|
||||
contentScale = ContentScale.Fit,
|
||||
model = uri,
|
||||
)
|
||||
|
||||
IconButton(
|
||||
modifier = Modifier.align(alignment = Alignment.TopEnd),
|
||||
onClick = onDismissRequest,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Close,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(uiMode = UI_MODE_NIGHT_NO)
|
||||
@Preview(uiMode = UI_MODE_NIGHT_YES)
|
||||
private fun ImageDialogPreview() {
|
||||
LexiconTheme {
|
||||
ImageDialog(
|
||||
url = remember { mutableStateOf(Uri.parse(R.drawable.im_brulkhai.uri)) },
|
||||
onDismissRequest = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,6 @@ import androidx.navigation.NavOptionsBuilder
|
|||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.AUTHENTICATION_ROUTE
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.HOME_ROUTE
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.composableAuthentication
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.composableCharacterSheet
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.composableHome
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
|||
import android.net.Uri
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
|
|
@ -18,7 +19,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
|
|
@ -58,6 +58,8 @@ import com.pixelized.rplexicon.R
|
|||
import com.pixelized.rplexicon.model.Lexicon
|
||||
import com.pixelized.rplexicon.ui.composable.AsyncImage
|
||||
import com.pixelized.rplexicon.ui.composable.BackgroundImage
|
||||
import com.pixelized.rplexicon.ui.composable.FullScreenImageHandler
|
||||
import com.pixelized.rplexicon.ui.composable.FullScreenImageViewModel
|
||||
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.navigateToCharacterSheet
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
|
|
@ -68,7 +70,6 @@ import com.pixelized.rplexicon.utilitary.extentions.highlightRegex
|
|||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
import com.pixelized.rplexicon.utilitary.extentions.scrollOffset
|
||||
import com.pixelized.rplexicon.utilitary.extentions.searchCriterion
|
||||
import com.skydoves.landscapist.ImageOptions
|
||||
|
||||
@Stable
|
||||
data class LexiconDetailUio(
|
||||
|
|
@ -160,6 +161,7 @@ fun LexiconDetailUio.annotate(): AnnotatedLexiconDetailUio {
|
|||
@Composable
|
||||
fun LexiconDetailScreen(
|
||||
viewModel: LexiconDetailViewModel = hiltViewModel(),
|
||||
imageViewModel: FullScreenImageViewModel = hiltViewModel(),
|
||||
) {
|
||||
val screen = LocalScreenNavHost.current
|
||||
|
||||
|
|
@ -170,6 +172,11 @@ fun LexiconDetailScreen(
|
|||
haveCharacterSheet = viewModel.haveCharacterSheet,
|
||||
onBack = { screen.popBackStack() },
|
||||
onCharacterSheet = { screen.navigateToCharacterSheet(name = it) },
|
||||
onImage = { imageViewModel.showDetail(it) }
|
||||
)
|
||||
|
||||
FullScreenImageHandler(
|
||||
viewModel = imageViewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -183,6 +190,7 @@ private fun LexiconDetailContent(
|
|||
haveCharacterSheet: State<Boolean>,
|
||||
onBack: () -> Unit,
|
||||
onCharacterSheet: (String) -> Unit,
|
||||
onImage: (Uri) -> Unit,
|
||||
) {
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
val typography = MaterialTheme.typography
|
||||
|
|
@ -230,7 +238,7 @@ private fun LexiconDetailContent(
|
|||
.fillMaxWidth()
|
||||
.aspectRatio(ratio = 1f)
|
||||
.scrollOffset(scrollState = state) { -it / 2 },
|
||||
model = { uri.toString() },
|
||||
model = uri,
|
||||
)
|
||||
}
|
||||
Column(
|
||||
|
|
@ -384,17 +392,16 @@ private fun LexiconDetailContent(
|
|||
) {
|
||||
items(items = annotatedItem.portrait) {
|
||||
AsyncImage(
|
||||
modifier = Modifier.sizeIn(
|
||||
minWidth = maxSize / 2,
|
||||
maxWidth = maxSize,
|
||||
minHeight = maxSize,
|
||||
maxHeight = maxSize,
|
||||
),
|
||||
imageOptions = ImageOptions(
|
||||
contentScale = ContentScale.FillHeight
|
||||
),
|
||||
imageModel = { it },
|
||||
previewPlaceholder = R.drawable.im_brulkhai,
|
||||
modifier = Modifier
|
||||
.clickable { onImage(it) }
|
||||
.sizeIn(
|
||||
minWidth = maxSize / 2,
|
||||
maxWidth = maxSize,
|
||||
minHeight = maxSize,
|
||||
maxHeight = maxSize,
|
||||
),
|
||||
contentScale = ContentScale.FillHeight,
|
||||
model = it,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -459,6 +466,7 @@ private fun LexiconDetailPreview() {
|
|||
haveCharacterSheet = remember { mutableStateOf(true) },
|
||||
onBack = { },
|
||||
onCharacterSheet = { },
|
||||
onImage = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.pixelized.rplexicon.ui.screens.location.detail
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.animateOffsetAsState
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
|
|
@ -26,21 +25,20 @@ import androidx.compose.ui.graphics.drawscope.DrawScope
|
|||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.rplexicon.ui.composable.AsyncImage
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
import com.skydoves.landscapist.ImageOptions
|
||||
|
||||
@Composable
|
||||
fun FantasyMap(
|
||||
modifier: Modifier = Modifier,
|
||||
state: FantasyMapState,
|
||||
model: () -> Any?,
|
||||
imageOptions: ImageOptions = ImageOptions(),
|
||||
@DrawableRes previewPlaceholder: Int,
|
||||
model: Any?,
|
||||
contentScale: ContentScale = ContentScale.Fit,
|
||||
items: State<List<AnnotatedMarqueeUio>>,
|
||||
highlight: State<Offset>,
|
||||
selectedItem: State<Int>,
|
||||
|
|
@ -163,9 +161,8 @@ fun FantasyMap(
|
|||
}
|
||||
)
|
||||
},
|
||||
imageModel = model,
|
||||
imageOptions = imageOptions,
|
||||
previewPlaceholder = previewPlaceholder,
|
||||
model = model,
|
||||
contentScale = contentScale,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,6 @@ import com.pixelized.rplexicon.ui.composable.Handle
|
|||
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
import com.skydoves.landscapist.ImageOptions
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -292,9 +291,8 @@ private fun LocationContent(
|
|||
)
|
||||
.offset(scrollState = scrollState),
|
||||
state = fantasyMapState,
|
||||
model = { item.value.map },
|
||||
previewPlaceholder = R.drawable.im_brulkhai,
|
||||
imageOptions = ImageOptions(contentScale = ContentScale.Fit),
|
||||
model = item.value.map,
|
||||
contentScale = ContentScale.Fit,
|
||||
items = remember { derivedStateOf { item.value.marquees } },
|
||||
selectedItem = selectedIndex,
|
||||
highlight = mapHighlight,
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ private fun QuestDetailContent(
|
|||
) {
|
||||
BackgroundImage(
|
||||
modifier = Modifier.matchParentSize(),
|
||||
model = { annotatedQuest.background },
|
||||
model = annotatedQuest.background,
|
||||
)
|
||||
if (annotatedQuest.completed) {
|
||||
Text(
|
||||
|
|
|
|||
|
|
@ -1,65 +0,0 @@
|
|||
package com.pixelized.rplexicon.utilitary
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import com.bumptech.glide.GlideBuilder
|
||||
import com.bumptech.glide.annotation.GlideModule
|
||||
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory
|
||||
import com.bumptech.glide.module.AppGlideModule
|
||||
|
||||
|
||||
@GlideModule
|
||||
class LexiconGlideModule : AppGlideModule() {
|
||||
override fun applyOptions(context: Context, builder: GlideBuilder) {
|
||||
val diskCacheSizeBytes = 1024 * 1024 * 100 // 100 MB
|
||||
builder.setDiskCache(InternalCacheDiskCacheFactory(context, diskCacheSizeBytes.toLong()))
|
||||
}
|
||||
}
|
||||
|
||||
@Stable
|
||||
class GlideLoadingTransition(
|
||||
target: MutableState<Float>,
|
||||
alpha: State<Float>
|
||||
) {
|
||||
var target by target
|
||||
val alpha by alpha
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberLoadingTransition(model: () -> Any?): GlideLoadingTransition {
|
||||
val isInEditMode = LocalView.current.isInEditMode
|
||||
|
||||
return if (isInEditMode) {
|
||||
remember {
|
||||
GlideLoadingTransition(
|
||||
target = mutableFloatStateOf(1f),
|
||||
alpha = mutableFloatStateOf(1f),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val key = model()
|
||||
val target = remember(key) {
|
||||
mutableFloatStateOf(0f)
|
||||
}
|
||||
val alpha = animateFloatAsState(
|
||||
targetValue = target.value,
|
||||
label = "LoadingAlphaTransition"
|
||||
)
|
||||
remember(key) {
|
||||
GlideLoadingTransition(
|
||||
target = target,
|
||||
alpha = alpha,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,11 +2,15 @@ package com.pixelized.rplexicon.utilitary.extentions
|
|||
|
||||
import androidx.compose.animation.core.FiniteAnimationSpec
|
||||
import androidx.compose.animation.core.Transition
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.animateOffsetAsState
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.gestures.detectTransformGestures
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
|
@ -19,12 +23,20 @@ import androidx.compose.material3.surfaceColorAtElevation
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.graphics.TransformOrigin
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -49,6 +61,33 @@ fun Modifier.placeholder(
|
|||
)
|
||||
}
|
||||
|
||||
fun Modifier.zoomable() = composed {
|
||||
var offset by remember { mutableStateOf(Offset.Zero) }
|
||||
var zoom by remember { mutableFloatStateOf(1f) }
|
||||
|
||||
return@composed this
|
||||
.pointerInput(Unit) {
|
||||
detectTransformGestures(
|
||||
onGesture = { centroid, pan, gestureZoom, _ ->
|
||||
val newScale = maxOf(1f, zoom * gestureZoom)
|
||||
val newOffset = (offset + centroid / zoom) - (centroid / newScale + pan / zoom)
|
||||
offset = Offset(
|
||||
newOffset.x.coerceIn(0f, (size.width / zoom) * (zoom - 1f)),
|
||||
newOffset.y.coerceIn(0f, (size.height / zoom) * (zoom - 1f))
|
||||
)
|
||||
zoom = maxOf(1f, zoom * gestureZoom)
|
||||
}
|
||||
)
|
||||
}
|
||||
.graphicsLayer {
|
||||
translationX = -offset.x * zoom
|
||||
translationY = -offset.y * zoom
|
||||
scaleX = zoom
|
||||
scaleY = zoom
|
||||
transformOrigin = TransformOrigin(0f, 0f)
|
||||
}
|
||||
}
|
||||
|
||||
@Stable
|
||||
fun Modifier.cell() = composed {
|
||||
Modifier
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue