mirror of
https://github.com/LiveFastEatTrashRaccoon/RaccoonForLemmy.git
synced 2025-02-03 16:37:32 +01:00
enhancement: select scale mode in image detail (#811)
This commit is contained in:
parent
f6a0586cc4
commit
7248de98df
@ -29,7 +29,6 @@ actual fun CustomImage(
|
||||
contentDescription: String?,
|
||||
quality: FilterQuality,
|
||||
contentScale: ContentScale,
|
||||
dynamicallyAdjustScale: Boolean,
|
||||
alignment: Alignment,
|
||||
contentAlignment: Alignment,
|
||||
alpha: Float,
|
||||
@ -38,21 +37,20 @@ actual fun CustomImage(
|
||||
onFailure: @Composable (BoxScope.(Throwable) -> Unit)?,
|
||||
) {
|
||||
var shouldBeRendered by remember(autoload) { mutableStateOf(autoload) }
|
||||
var scale by remember { mutableStateOf(contentScale) }
|
||||
var painterState: AsyncImagePainter.State by remember {
|
||||
mutableStateOf(AsyncImagePainter.State.Empty)
|
||||
}
|
||||
Box(
|
||||
modifier = modifier,
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
if (shouldBeRendered) {
|
||||
var painterState: AsyncImagePainter.State by remember {
|
||||
mutableStateOf(AsyncImagePainter.State.Empty)
|
||||
}
|
||||
AsyncImage(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
model = url,
|
||||
contentDescription = contentDescription,
|
||||
filterQuality = quality,
|
||||
contentScale = scale,
|
||||
contentScale = contentScale,
|
||||
alignment = alignment,
|
||||
alpha = alpha,
|
||||
colorFilter = colorFilter,
|
||||
@ -64,9 +62,6 @@ actual fun CustomImage(
|
||||
},
|
||||
onSuccess = {
|
||||
painterState = it
|
||||
if (it.result.drawable.intrinsicHeight > it.result.drawable.intrinsicWidth && dynamicallyAdjustScale) {
|
||||
scale = ContentScale.FillHeight
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -18,7 +18,6 @@ expect fun CustomImage(
|
||||
contentDescription: String? = null,
|
||||
quality: FilterQuality = FilterQuality.Medium,
|
||||
contentScale: ContentScale = ContentScale.Fit,
|
||||
dynamicallyAdjustScale: Boolean = false,
|
||||
alignment: Alignment = Alignment.Center,
|
||||
contentAlignment: Alignment = Alignment.Center,
|
||||
alpha: Float = DefaultAlpha,
|
||||
|
@ -1,14 +1,12 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.core.commonui.components
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.InfiniteRepeatableSpec
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
import androidx.compose.animation.core.rememberInfiniteTransition
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.animateZoomBy
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.gestures.rememberTransformableState
|
||||
import androidx.compose.foundation.gestures.transformable
|
||||
import androidx.compose.foundation.gestures.detectTransformGestures
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
@ -16,74 +14,85 @@ import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.FilterQuality
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.l10n.LocalXmlStrings
|
||||
import kotlinx.coroutines.launch
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
private const val LOADING_ANIMATION_DURATION = 1000
|
||||
|
||||
@Composable
|
||||
fun ZoomableImage(
|
||||
contentScale: ContentScale = ContentScale.Fit,
|
||||
modifier: Modifier = Modifier,
|
||||
url: String,
|
||||
autoLoadImages: Boolean = false,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
var scale by remember {
|
||||
mutableStateOf(1f)
|
||||
}
|
||||
var offset by remember {
|
||||
mutableStateOf(Offset.Zero)
|
||||
}
|
||||
var visible by remember {
|
||||
mutableStateOf(true)
|
||||
}
|
||||
|
||||
LaunchedEffect(contentScale) {
|
||||
visible = false
|
||||
delay(50)
|
||||
visible = true
|
||||
}
|
||||
|
||||
BoxWithConstraints(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Black),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
val transformableState =
|
||||
rememberTransformableState { zoomChange, panChange, _ ->
|
||||
scale = (scale * zoomChange).coerceIn(1f, 16f)
|
||||
|
||||
AnimatedVisibility(visible = visible) {
|
||||
CustomImage(
|
||||
modifier = Modifier
|
||||
.onClick(
|
||||
onDoubleClick = {
|
||||
if (scale > 1f) {
|
||||
scale = 1f
|
||||
offset = Offset.Zero
|
||||
} else {
|
||||
scale *= 2.5f
|
||||
}
|
||||
},
|
||||
)
|
||||
.pointerInput(Unit) {
|
||||
detectTransformGestures(
|
||||
onGesture = { _, pan, gestureZoom, _ ->
|
||||
val extraWidth = (scale - 1) * constraints.maxWidth
|
||||
val extraHeight = (scale - 1) * constraints.maxHeight
|
||||
|
||||
val maxX = extraWidth / 2
|
||||
val maxY = extraHeight / 2
|
||||
|
||||
offset = Offset(
|
||||
x = (offset.x + scale * panChange.x).coerceIn(-maxX, maxX),
|
||||
y = (offset.y + scale * panChange.y).coerceIn(-maxY, maxY),
|
||||
)
|
||||
}
|
||||
scale = (scale * gestureZoom).coerceIn(1f, 16f)
|
||||
|
||||
CustomImage(
|
||||
modifier = Modifier
|
||||
.clip(RectangleShape)
|
||||
.fillMaxSize()
|
||||
.background(Color.Black)
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures(
|
||||
onDoubleTap = {
|
||||
scope.launch {
|
||||
if (scale != 1f) {
|
||||
transformableState.animateZoomBy(1 / scale)
|
||||
offset = Offset.Zero
|
||||
offset = if (scale > 1) {
|
||||
Offset(
|
||||
x = (offset.x + pan.x * scale).coerceIn(-maxX, maxX),
|
||||
y = (offset.y + pan.y * scale).coerceIn(-maxY, maxY),
|
||||
)
|
||||
} else {
|
||||
transformableState.animateZoomBy(2f)
|
||||
}
|
||||
Offset.Zero
|
||||
}
|
||||
},
|
||||
)
|
||||
@ -93,11 +102,9 @@ fun ZoomableImage(
|
||||
scaleY = scale,
|
||||
translationX = offset.x,
|
||||
translationY = offset.y,
|
||||
)
|
||||
.transformable(transformableState),
|
||||
),
|
||||
url = url,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
dynamicallyAdjustScale = true,
|
||||
contentScale = contentScale,
|
||||
quality = FilterQuality.High,
|
||||
autoload = autoLoadImages,
|
||||
onFailure = {
|
||||
@ -129,4 +136,5 @@ fun ZoomableImage(
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,6 @@ actual fun CustomImage(
|
||||
contentDescription: String?,
|
||||
quality: FilterQuality,
|
||||
contentScale: ContentScale,
|
||||
dynamicallyAdjustScale: Boolean,
|
||||
alignment: Alignment,
|
||||
contentAlignment: Alignment,
|
||||
alpha: Float,
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">متصفح خارجي</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">علامات التبويب المخصصة</string>
|
||||
<string name="settings_full_width_images">صور بالعرض الكامل</string>
|
||||
<string name="content_scale_fit">ناسب الحجم</string>
|
||||
<string name="content_scale_fill_width">ملء العرض</string>
|
||||
<string name="content_scale_fill_height">ملء الارتفاع</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Браузър esterno</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Персонализирани раздели</string>
|
||||
<string name="settings_full_width_images">Изображения с пълна ширина</string>
|
||||
<string name="content_scale_fit">Съответства на размера</string>
|
||||
<string name="content_scale_fill_width">Попълнете ширината</string>
|
||||
<string name="content_scale_fill_height">Запълнете височината</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Prohlížeč esterno</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Vlastní karty</string>
|
||||
<string name="settings_full_width_images">Obrázky plné šířky</string>
|
||||
<string name="content_scale_fit">Přizpůsobit velikost</string>
|
||||
<string name="content_scale_fill_width">Vyplňte šířku</string>
|
||||
<string name="content_scale_fill_height">Vyplňte výšku</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Browser esterno</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Brugerdefinerede faner</string>
|
||||
<string name="settings_full_width_images">Billeder i fuld bredde</string>
|
||||
<string name="content_scale_fit">Tilpas størrelsen</string>
|
||||
<string name="content_scale_fill_width">Fyld bredden</string>
|
||||
<string name="content_scale_fill_height">Fyld højden</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Externer Browser</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Benutzerdefinierte Registerkarten</string>
|
||||
<string name="settings_full_width_images">Bilder in voller Breite</string>
|
||||
<string name="content_scale_fit">Passen Sie die Größe an</string>
|
||||
<string name="content_scale_fill_width">Füllen Sie die Breite</string>
|
||||
<string name="content_scale_fill_height">Füllen Sie die Höhe aus</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Εξωτερικό πρόγραμμα περιήγησης</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Προσαρμοσμένες καρτέλες</string>
|
||||
<string name="settings_full_width_images">Εικόνες πλήρους πλάτους</string>
|
||||
<string name="content_scale_fit">Ταιριάζει στο μέγεθος</string>
|
||||
<string name="content_scale_fill_width">Γεμίστε το πλάτος</string>
|
||||
<string name="content_scale_fill_height">Συμπληρώστε το ύψος</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Ekstera retumilo</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Propraj langetoj</string>
|
||||
<string name="settings_full_width_images">Plenlarĝaj bildoj</string>
|
||||
<string name="content_scale_fit">Konveni la grandecon</string>
|
||||
<string name="content_scale_fill_width">Plenigi la larĝon</string>
|
||||
<string name="content_scale_fill_height">Plenigi la altecon</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Navegador externo</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Pestañas personalizadas</string>
|
||||
<string name="settings_full_width_images">Imágenes de ancho completo</string>
|
||||
<string name="content_scale_fit">Ajustar el tamaño</string>
|
||||
<string name="content_scale_fill_width">Llenar el ancho</string>
|
||||
<string name="content_scale_fill_height">Llenar la altura</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Väline brauser</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Kohandatud vahelehed</string>
|
||||
<string name="settings_full_width_images">Täislaiuses pildid</string>
|
||||
<string name="content_scale_fit">Sobivad suurusele</string>
|
||||
<string name="content_scale_fill_width">Täida laius</string>
|
||||
<string name="content_scale_fill_height">Täida kõrgus</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Ulkoinen selain</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Mukautetut välilehdet</string>
|
||||
<string name="settings_full_width_images">Täysleveät kuvat</string>
|
||||
<string name="content_scale_fit">Sopiva kokoon</string>
|
||||
<string name="content_scale_fill_width">Täytä leveys</string>
|
||||
<string name="content_scale_fill_height">Täytä korkeus</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Navigateur externe</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Onglets personnalisés</string>
|
||||
<string name="settings_full_width_images">Images pleine largeur</string>
|
||||
<string name="content_scale_fit">Ajuster la taille</string>
|
||||
<string name="content_scale_fill_width">Remplissez la largeur</string>
|
||||
<string name="content_scale_fill_height">Remplissez la hauteur</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Brabhsálaí seachtrach</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Cluaisíní saincheaptha</string>
|
||||
<string name="settings_full_width_images">Íomhánna ar leithead iomlán</string>
|
||||
<string name="content_scale_fit">Fit an méid</string>
|
||||
<string name="content_scale_fill_width">Líon isteach an leithead</string>
|
||||
<string name="content_scale_fill_height">Líon an airde</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Vanjski preglednik</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Prilagođene kartice</string>
|
||||
<string name="settings_full_width_images">Slike pune širine</string>
|
||||
<string name="content_scale_fit">Odgovara veličini</string>
|
||||
<string name="content_scale_fill_width">Ispunite širinu</string>
|
||||
<string name="content_scale_fill_height">Ispunite visinu</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Külső böngésző</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Egyéni lapok</string>
|
||||
<string name="settings_full_width_images">Teljes szélességű képek</string>
|
||||
<string name="content_scale_fit">Illessze a méretet</string>
|
||||
<string name="content_scale_fill_width">Töltse ki a szélességet</string>
|
||||
<string name="content_scale_fill_height">Töltse ki a magasságot</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Browser esterno</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Schede personalizzate</string>
|
||||
<string name="settings_full_width_images">Immagini a larghezza completa</string>
|
||||
<string name="content_scale_fit">Adatta alla dimensione</string>
|
||||
<string name="content_scale_fill_width">Adatta alla larghezza</string>
|
||||
<string name="content_scale_fill_height">Adatta alla altezza</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Išorinė naršyklė</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Tinkinti skirtukai</string>
|
||||
<string name="settings_full_width_images">Viso pločio vaizdai</string>
|
||||
<string name="content_scale_fit">Atitinka dydį</string>
|
||||
<string name="content_scale_fill_width">Užpildykite plotį</string>
|
||||
<string name="content_scale_fill_height">Užpildykite aukštį</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Ārējā pārlūkprogramma</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Pielāgotas cilnes</string>
|
||||
<string name="settings_full_width_images">Pilna platuma attēli</string>
|
||||
<string name="content_scale_fit">Atbilst izmēram</string>
|
||||
<string name="content_scale_fill_width">Aizpildiet platumu</string>
|
||||
<string name="content_scale_fill_height">Aizpildiet augstumu</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Browser estern</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Tabs tad-dwana</string>
|
||||
<string name="settings_full_width_images">Immaġini ta \'wisa\' sħiħ</string>
|
||||
<string name="content_scale_fit">Waħħal id-daqs</string>
|
||||
<string name="content_scale_fill_width">Imla l-wisa</string>
|
||||
<string name="content_scale_fill_height">Imla l-għoli</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Externe browser</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Aangepaste tabbladen</string>
|
||||
<string name="settings_full_width_images">Afbeeldingen over de volledige breedte</string>
|
||||
<string name="content_scale_fit">Pas de maat aan</string>
|
||||
<string name="content_scale_fill_width">Vul de breedte</string>
|
||||
<string name="content_scale_fill_height">Vul de hoogte</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Ekstern nettleser</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Egendefinerte faner</string>
|
||||
<string name="settings_full_width_images">Bilder i full bredde</string>
|
||||
<string name="content_scale_fit">Passer størrelsen</string>
|
||||
<string name="content_scale_fill_width">Fyll bredden</string>
|
||||
<string name="content_scale_fill_height">Fyll høyden</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Przeglądarka zewnętrzna</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Zakładki niestandardowe</string>
|
||||
<string name="settings_full_width_images">Obrazy o pełnej szerokości</string>
|
||||
<string name="content_scale_fit">Dopasuj rozmiar</string>
|
||||
<string name="content_scale_fill_width">Wypełnij szerokość</string>
|
||||
<string name="content_scale_fill_height">Wypełnij wysokość</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Navegador externo</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Tabs personalizadas</string>
|
||||
<string name="settings_full_width_images">Imagens de largura total</string>
|
||||
<string name="content_scale_fit">Ajuste o tamanho</string>
|
||||
<string name="content_scale_fill_width">Preencha a largura</string>
|
||||
<string name="content_scale_fill_height">Preencha a altura</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Navegador externo</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Guias personalizadas</string>
|
||||
<string name="settings_full_width_images">Imagens de largura total</string>
|
||||
<string name="content_scale_fit">Ajuste o tamanho</string>
|
||||
<string name="content_scale_fill_width">Preencha a largura</string>
|
||||
<string name="content_scale_fill_height">Preencha a altura</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Browser extern</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">File personalizate</string>
|
||||
<string name="settings_full_width_images">Imagini la lățime completă</string>
|
||||
<string name="content_scale_fit">Se potrivește mărimii</string>
|
||||
<string name="content_scale_fill_width">Umpleți lățimea</string>
|
||||
<string name="content_scale_fill_height">Umpleți înălțimea</string>
|
||||
</resources>
|
||||
|
@ -389,4 +389,7 @@
|
||||
<string name="settings_url_opening_mode_external">Внешний браузер</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Пользовательские вкладки</string>
|
||||
<string name="settings_full_width_images">Изображения в полную ширину</string>
|
||||
<string name="content_scale_fit">Подогнать размер</string>
|
||||
<string name="content_scale_fill_width">Заполните ширину</string>
|
||||
<string name="content_scale_fill_height">Заполните высоту</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Extern webbläsare</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Anpassade flikar</string>
|
||||
<string name="settings_full_width_images">Bilder i full bredd</string>
|
||||
<string name="content_scale_fit">Passa storleken</string>
|
||||
<string name="content_scale_fill_width">Fyll bredden</string>
|
||||
<string name="content_scale_fill_height">Fyll höjden</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Externý prehliadač</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Vlastné karty</string>
|
||||
<string name="settings_full_width_images">Obrázky v plnej šírke</string>
|
||||
<string name="content_scale_fit">Prispôsobte sa veľkosti</string>
|
||||
<string name="content_scale_fill_width">Vyplňte šírku</string>
|
||||
<string name="content_scale_fill_height">Vyplňte výšku</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Zunanji brskalnik</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Zavihki po meri</string>
|
||||
<string name="settings_full_width_images">Slike polne širine</string>
|
||||
<string name="content_scale_fit">Prilagodite velikost</string>
|
||||
<string name="content_scale_fill_width">Izpolnite širino</string>
|
||||
<string name="content_scale_fill_height">Izpolnite višino</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Shfletuesi i jashtëm</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Skedat e personalizuara</string>
|
||||
<string name="settings_full_width_images">Imazhe me gjerësi të plotë</string>
|
||||
<string name="content_scale_fit">Përshtatet me madhësinë</string>
|
||||
<string name="content_scale_fill_width">Mbushni gjerësinë</string>
|
||||
<string name="content_scale_fill_height">Mbushni lartësinë</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Екстерни претраживач</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Прилагођене картице</string>
|
||||
<string name="settings_full_width_images">Слике пуне ширине</string>
|
||||
<string name="content_scale_fit">Прилагодите величину</string>
|
||||
<string name="content_scale_fill_width">Попуните ширину</string>
|
||||
<string name="content_scale_fill_height">Попуните висину</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">ilo ante</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">lipu pi wile jan</string>
|
||||
<string name="settings_full_width_images">supa suli sitelen</string>
|
||||
<string name="content_scale_fit">suli tu</string>
|
||||
<string name="content_scale_fill_width">suli poka</string>
|
||||
<string name="content_scale_fill_height">suli sewi</string>
|
||||
</resources>
|
||||
|
@ -390,4 +390,7 @@
|
||||
<string name="settings_url_opening_mode_external">Harici tarayıcı</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Özel sekmeler</string>
|
||||
<string name="settings_full_width_images">Tam genişlikte resimler</string>
|
||||
<string name="content_scale_fit">Boyutu sığdır</string>
|
||||
<string name="content_scale_fill_width">Genişliği doldurun</string>
|
||||
<string name="content_scale_fill_height">Yüksekliği doldurun</string>
|
||||
</resources>
|
||||
|
@ -389,4 +389,7 @@
|
||||
<string name="settings_url_opening_mode_external">Зовнішній браузер</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Спеціальні вкладки</string>
|
||||
<string name="settings_full_width_images">Зображення в повну ширину</string>
|
||||
<string name="content_scale_fit">Підходить за розміром</string>
|
||||
<string name="content_scale_fill_width">Заповнити ширину</string>
|
||||
<string name="content_scale_fill_height">Заповнити висоту</string>
|
||||
</resources>
|
||||
|
@ -398,4 +398,7 @@
|
||||
<string name="settings_url_opening_mode_external">External browser</string>
|
||||
<string name="settings_url_opening_mode_custom_tabs">Custom tabs</string>
|
||||
<string name="settings_full_width_images">Full width images</string>
|
||||
<string name="content_scale_fit">Aspect fit</string>
|
||||
<string name="content_scale_fill_width">Fill width</string>
|
||||
<string name="content_scale_fill_height">Fill height</string>
|
||||
</resources>
|
||||
|
@ -1,7 +1,9 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.core.utils.imagepreload
|
||||
|
||||
import android.content.Context
|
||||
import coil.annotation.ExperimentalCoilApi
|
||||
import coil.imageLoader
|
||||
import coil.memory.MemoryCache
|
||||
import coil.request.ImageRequest
|
||||
|
||||
class DefaultImagePreloadManager(
|
||||
@ -14,4 +16,10 @@ class DefaultImagePreloadManager(
|
||||
.build()
|
||||
context.imageLoader.enqueue(request)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoilApi::class)
|
||||
override fun remove(url: String) {
|
||||
context.imageLoader.memoryCache?.remove(MemoryCache.Key(url))
|
||||
context.imageLoader.diskCache?.remove(url)
|
||||
}
|
||||
}
|
@ -2,5 +2,6 @@ package com.github.diegoberaldin.raccoonforlemmy.core.utils.imagepreload
|
||||
|
||||
interface ImagePreloadManager {
|
||||
fun preload(url: String)
|
||||
fun remove(url: String)
|
||||
}
|
||||
|
||||
|
@ -5,4 +5,8 @@ class DefaultImagePreloadManager() : ImagePreloadManager {
|
||||
override fun preload(url: String) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
override fun remove(url: String) {
|
||||
// no-op
|
||||
}
|
||||
}
|
@ -189,7 +189,7 @@ internal object ProfileMainScreen : Tab {
|
||||
if (uiState.logged == true) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(end = Spacing.s)
|
||||
.padding(horizontal = Spacing.xs)
|
||||
.onClick(
|
||||
onClick = {
|
||||
notificationCenter.send(NotificationCenterEvent.ProfileSideMenuAction.ManageAccounts)
|
||||
|
@ -94,7 +94,7 @@ class CommunityInfoScreen(
|
||||
},
|
||||
actions = {
|
||||
Icon(
|
||||
modifier = Modifier.padding(end = Spacing.s).onClick(
|
||||
modifier = Modifier.padding(horizontal = Spacing.xs).onClick(
|
||||
onClick = {
|
||||
navigationCoordinator.closeSideMenu()
|
||||
},
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.unit.zoomableimage
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import cafe.adriel.voyager.core.model.ScreenModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
|
||||
|
||||
@ -11,13 +12,15 @@ interface ZoomableImageMviModel :
|
||||
sealed interface Intent {
|
||||
data class SaveToGallery(
|
||||
val source: String,
|
||||
val url: String,
|
||||
) : Intent
|
||||
|
||||
data class ChangeContentScale(val contentScale: ContentScale) : Intent
|
||||
}
|
||||
|
||||
data class UiState(
|
||||
val loading: Boolean = false,
|
||||
val autoLoadImages: Boolean = true,
|
||||
val contentScale: ContentScale = ContentScale.FillWidth,
|
||||
)
|
||||
|
||||
sealed interface Effect {
|
||||
|
@ -1,13 +1,16 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.unit.zoomableimage
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.AspectRatio
|
||||
import androidx.compose.material.icons.filled.Download
|
||||
import androidx.compose.material.icons.filled.Share
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@ -15,31 +18,43 @@ import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Snackbar
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.layout.positionInParent
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.koin.getScreenModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CustomDropDown
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.ProgressHud
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.ZoomableImage
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals.ShareImageBottomSheet
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.l10n.LocalXmlStrings
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.di.getDrawerCoordinator
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.di.getNavigationCoordinator
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.getScreenModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterEvent
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.di.getNotificationCenter
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.utils.share.getShareHelper
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.utils.toLocalDp
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
class ZoomableImageScreen(
|
||||
private val url: String,
|
||||
@ -50,7 +65,7 @@ class ZoomableImageScreen(
|
||||
@Composable
|
||||
override fun Content() {
|
||||
|
||||
val model = getScreenModel<ZoomableImageMviModel>()
|
||||
val model = getScreenModel<ZoomableImageMviModel>(tag = url, parameters = { parametersOf(url) })
|
||||
val uiState by model.uiState.collectAsState()
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val successMessage = LocalXmlStrings.current.messageOperationSuccessful
|
||||
@ -100,10 +115,7 @@ class ZoomableImageScreen(
|
||||
.onClick(
|
||||
onClick = {
|
||||
model.reduce(
|
||||
ZoomableImageMviModel.Intent.SaveToGallery(
|
||||
url = url,
|
||||
source = source,
|
||||
)
|
||||
ZoomableImageMviModel.Intent.SaveToGallery(source)
|
||||
)
|
||||
},
|
||||
),
|
||||
@ -130,6 +142,59 @@ class ZoomableImageScreen(
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
|
||||
// content scale option menu
|
||||
Box {
|
||||
val options = buildList {
|
||||
this += ContentScale.Fit
|
||||
this += ContentScale.FillWidth
|
||||
this += ContentScale.FillHeight
|
||||
}
|
||||
var optionsExpanded by remember { mutableStateOf(false) }
|
||||
var optionsOffset by remember { mutableStateOf(Offset.Zero) }
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = Spacing.xs)
|
||||
.onGloballyPositioned {
|
||||
optionsOffset = it.positionInParent()
|
||||
}.onClick(
|
||||
onClick = {
|
||||
optionsExpanded = true
|
||||
},
|
||||
),
|
||||
imageVector = Icons.Default.AspectRatio,
|
||||
contentDescription = null,
|
||||
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onBackground),
|
||||
)
|
||||
CustomDropDown(
|
||||
expanded = optionsExpanded,
|
||||
onDismiss = {
|
||||
optionsExpanded = false
|
||||
},
|
||||
offset = DpOffset(
|
||||
x = optionsOffset.x.toLocalDp(),
|
||||
y = optionsOffset.y.toLocalDp(),
|
||||
),
|
||||
) {
|
||||
options.forEach { option ->
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
val text = when (option) {
|
||||
ContentScale.FillHeight -> LocalXmlStrings.current.contentScaleFillHeight
|
||||
ContentScale.FillWidth -> LocalXmlStrings.current.contentScaleFillWidth
|
||||
else -> LocalXmlStrings.current.contentScaleFit
|
||||
}
|
||||
Text(text)
|
||||
},
|
||||
onClick = {
|
||||
optionsExpanded = false
|
||||
model.reduce(ZoomableImageMviModel.Intent.ChangeContentScale(option))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
)
|
||||
},
|
||||
@ -142,7 +207,8 @@ class ZoomableImageScreen(
|
||||
)
|
||||
}
|
||||
},
|
||||
content = { paddingValues ->
|
||||
content =
|
||||
{ paddingValues ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues)
|
||||
@ -153,6 +219,7 @@ class ZoomableImageScreen(
|
||||
ZoomableImage(
|
||||
url = url,
|
||||
autoLoadImages = uiState.autoLoadImages,
|
||||
contentScale = uiState.contentScale,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.unit.zoomableimage
|
||||
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import cafe.adriel.voyager.core.model.screenModelScope
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter
|
||||
@ -8,6 +9,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.Sett
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.utils.datetime.epochMillis
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.utils.gallery.GalleryHelper
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.utils.gallery.download
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.utils.imagepreload.ImagePreloadManager
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.utils.share.ShareHelper
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.IO
|
||||
@ -17,10 +19,12 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class ZoomableImageViewModel(
|
||||
private val url: String,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val shareHelper: ShareHelper,
|
||||
private val galleryHelper: GalleryHelper,
|
||||
private val notificationCenter: NotificationCenter,
|
||||
private val imagePreloadManager: ImagePreloadManager,
|
||||
) : ZoomableImageMviModel,
|
||||
DefaultMviModel<ZoomableImageMviModel.Intent, ZoomableImageMviModel.UiState, ZoomableImageMviModel.Effect>(
|
||||
initialState = ZoomableImageMviModel.UiState(),
|
||||
@ -43,14 +47,12 @@ class ZoomableImageViewModel(
|
||||
|
||||
override fun reduce(intent: ZoomableImageMviModel.Intent) {
|
||||
when (intent) {
|
||||
is ZoomableImageMviModel.Intent.SaveToGallery -> downloadAndSave(
|
||||
folder = intent.source,
|
||||
url = intent.url,
|
||||
)
|
||||
is ZoomableImageMviModel.Intent.SaveToGallery -> downloadAndSave(intent.source)
|
||||
is ZoomableImageMviModel.Intent.ChangeContentScale -> changeContentScale(intent.contentScale)
|
||||
}
|
||||
}
|
||||
|
||||
private fun downloadAndSave(url: String, folder: String) {
|
||||
private fun downloadAndSave(folder: String) {
|
||||
val imageSourcePath = settingsRepository.currentSettings.value.imageSourcePath
|
||||
screenModelScope.launch {
|
||||
updateState { it.copy(loading = true) }
|
||||
@ -115,4 +117,11 @@ class ZoomableImageViewModel(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun changeContentScale(contentScale: ContentScale) {
|
||||
imagePreloadManager.remove(url)
|
||||
updateState {
|
||||
it.copy(contentScale = contentScale)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,12 +5,14 @@ import com.github.diegoberaldin.raccoonforlemmy.unit.zoomableimage.ZoomableImage
|
||||
import org.koin.dsl.module
|
||||
|
||||
val zoomableImageModule = module {
|
||||
factory<ZoomableImageMviModel> {
|
||||
factory<ZoomableImageMviModel> { params ->
|
||||
ZoomableImageViewModel(
|
||||
url = params[0],
|
||||
shareHelper = get(),
|
||||
galleryHelper = get(),
|
||||
settingsRepository = get(),
|
||||
notificationCenter = get(),
|
||||
imagePreloadManager = get(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user