enhancement: select scale mode in image detail (#811)

This commit is contained in:
Diego Beraldin 2024-05-09 22:49:02 +02:00 committed by GitHub
parent f6a0586cc4
commit 7248de98df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
47 changed files with 303 additions and 106 deletions

View File

@ -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
}
}
)

View File

@ -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,

View File

@ -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(
},
)
}
}
}

View File

@ -29,7 +29,6 @@ actual fun CustomImage(
contentDescription: String?,
quality: FilterQuality,
contentScale: ContentScale,
dynamicallyAdjustScale: Boolean,
alignment: Alignment,
contentAlignment: Alignment,
alpha: Float,

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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)
}
}

View File

@ -2,5 +2,6 @@ package com.github.diegoberaldin.raccoonforlemmy.core.utils.imagepreload
interface ImagePreloadManager {
fun preload(url: String)
fun remove(url: String)
}

View File

@ -5,4 +5,8 @@ class DefaultImagePreloadManager() : ImagePreloadManager {
override fun preload(url: String) {
// no-op
}
override fun remove(url: String) {
// no-op
}
}

View File

@ -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)

View File

@ -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()
},

View File

@ -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 {

View File

@ -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,
)
}
}

View File

@ -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)
}
}
}

View File

@ -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(),
)
}
}