feat: community-specific download directory (#581)

This commit is contained in:
Diego Beraldin 2024-03-07 23:43:56 +01:00 committed by GitHub
parent 1fd2f96a81
commit 9f04030a3e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
63 changed files with 286 additions and 55 deletions

View File

@ -25,6 +25,8 @@ fun SettingsIntValueRow(
onIncrement: () -> Unit,
onDecrement: () -> Unit,
) {
val fullColor = MaterialTheme.colorScheme.onBackground
val ancillaryColor = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.75f)
Row(
modifier = Modifier.padding(horizontal = Spacing.m),
verticalAlignment = Alignment.CenterVertically,
@ -35,13 +37,13 @@ fun SettingsIntValueRow(
Text(
text = title,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onBackground,
color = fullColor,
)
if (subtitle != null) {
Text(
text = subtitle,
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onBackground,
color = ancillaryColor,
)
}
}
@ -60,7 +62,7 @@ fun SettingsIntValueRow(
textAlign = TextAlign.Center,
text = value.toString(),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onBackground,
color = fullColor,
)
FeedbackButton(
imageVector = Icons.Default.ArrowCircleUp,

View File

@ -33,6 +33,7 @@ fun SettingsRow(
onTap: (() -> Unit)? = null,
) {
val fullColor = MaterialTheme.colorScheme.onBackground
val ancillaryColor = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.75f)
Row(
modifier = modifier
.padding(vertical = Spacing.s, horizontal = Spacing.m)
@ -64,7 +65,7 @@ fun SettingsRow(
Text(
text = subtitle,
style = MaterialTheme.typography.labelMedium,
color = fullColor,
color = ancillaryColor,
)
}
}

View File

@ -25,16 +25,18 @@ fun SettingsSwitchRow(
Column(
modifier = Modifier.weight(1f),
) {
val fullColor = MaterialTheme.colorScheme.onBackground
val ancillaryColor = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.75f)
Text(
text = title,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onBackground,
color = fullColor,
)
if (subtitle != null) {
Text(
text = subtitle,
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onBackground,
color = ancillaryColor,
)
}
}

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">هناك تغييرات لم يتم حفظها، هل أنت متأكد من أنك تريد الخروج؟</string>
<string name="button_no_stay">لا، ابق هنا</string>
<string name="button_yes_quit">نعم، الخروج</string>
<string name="settings_item_image_source_path">حفظ الصور في أدلة فرعية محددة</string>
<string name="settings_subtitle_image_source_path">استخدم المجتمع أو مقبض المستخدم في المسارات</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">Има незапазени промени, сигурни ли сте, че искате да излезете?</string>
<string name="button_no_stay">Не, остани тук</string>
<string name="button_yes_quit">Да, изход</string>
<string name="settings_item_image_source_path">Запазване на изображения в определени поддиректории</string>
<string name="settings_subtitle_image_source_path">използвайте общност или потребителски манипулатор в пътеки</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">Existují neuložené změny. Opravdu chcete skončit?</string>
<string name="button_no_stay">Ne, zůstaň tady</string>
<string name="button_yes_quit">Ano, odejít</string>
<string name="settings_item_image_source_path">Ukládejte obrázky do konkrétních podadresářů</string>
<string name="settings_subtitle_image_source_path">použijte komunitní nebo uživatelský popisovač v cestách</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">Der er ikke-gemte ændringer. Er du sikker på, at du vil afslutte?</string>
<string name="button_no_stay">Nej, bliv her</string>
<string name="button_yes_quit">Ja, exit</string>
<string name="settings_item_image_source_path">Gem billeder i specifikke undermapper</string>
<string name="settings_subtitle_image_source_path">bruge fællesskab eller brugerhåndtag i stier</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">Es gibt nicht gespeicherte Änderungen. Möchten Sie den Vorgang wirklich beenden?</string>
<string name="button_no_stay">Nein, bleib hier</string>
<string name="button_yes_quit">Ja, beenden</string>
<string name="settings_item_image_source_path">Speichern Sie Bilder in bestimmten Unterverzeichnissen</string>
<string name="settings_subtitle_image_source_path">Verwenden Sie Community- oder Benutzer-Handle in Pfaden</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">Υπάρχουν μη αποθηκευμένες αλλαγές, είστε βέβαιοι ότι θέλετε να βγείτε;</string>
<string name="button_no_stay">Όχι, μείνε εδώ</string>
<string name="button_yes_quit">Ναι, βγες</string>
<string name="settings_item_image_source_path">Αποθηκεύστε εικόνες σε συγκεκριμένους υποκαταλόγους</string>
<string name="settings_subtitle_image_source_path">χρήση κοινότητας ή χειρισμού χρήστη σε μονοπάτια</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">Estas nekonservitaj ŝanĝoj, ĉu vi certas, ke vi volas eliri?</string>
<string name="button_no_stay">Ne, resti ĉi tie</string>
<string name="button_yes_quit">Jes, eliri</string>
<string name="settings_item_image_source_path">Konservi bildojn en specifaj subdosierujoj</string>
<string name="settings_subtitle_image_source_path">uzi komunumon aŭ uzantan tenilon en vojoj</string>
</resources>

View File

@ -339,5 +339,7 @@
<string name="ban_item_duration_days">Duración (días)</string>
<string name="message_unsaved_changes">Hay cambios sin guardar, ¿está seguro de que quiere salir?</string>
<string name="button_no_stay">No, quedar aquí</string>
<string name="button_yes_quit">si, salir</string>
<string name="button_yes_quit">Sí, salir</string>
<string name="settings_item_image_source_path">Guardar imágenes en subdirectorios específicos</string>
<string name="settings_subtitle_image_source_path">utilizar la comunidad o el identificador de usuario en las rutas</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">On salvestamata muudatusi. Kas soovite kindlasti väljuda?</string>
<string name="button_no_stay">Ei, jää siia</string>
<string name="button_yes_quit">Jah, välju</string>
<string name="settings_item_image_source_path">Salvestage pildid kindlatesse alamkataloogidesse</string>
<string name="settings_subtitle_image_source_path">kasutada teedel kommuuni või kasutajakäepidet</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">Tallentamattomia muutoksia on. Haluatko varmasti poistua?</string>
<string name="button_no_stay">Ei, pysy täällä</string>
<string name="button_yes_quit">Kyllä, poistu</string>
<string name="settings_item_image_source_path">Tallenna kuvat tiettyihin alihakemistoihin</string>
<string name="settings_subtitle_image_source_path">käytä yhteisö- tai käyttäjäkahvaa poluissa</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">Il y a des modifications non enregistrées, êtes-vous sûr de vouloir quitter ?</string>
<string name="button_no_stay">Non, rester ici</string>
<string name="button_yes_quit">Oui, quitter</string>
<string name="settings_item_image_source_path">Enregistrez les images dans des sous-répertoires spécifiques</string>
<string name="settings_subtitle_image_source_path">utiliser la communauté ou le handle d\'utilisateur dans les chemins</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">Tá athruithe gan sábháil, an bhfuil tú cinnte gur mhaith leat scoir?</string>
<string name="button_no_stay">Ní hea, fan anseo</string>
<string name="button_yes_quit">Sea, scoir</string>
<string name="settings_item_image_source_path">Sábháil íomhánna i bhfo-eolaire ar leith</string>
<string name="settings_subtitle_image_source_path">úsáid pobail nó láimhseáil úsáideora i gcosáin</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">Postoje nespremljene promjene, jeste li sigurni da želite izaći?</string>
<string name="button_no_stay">Ne, ostani ovdje</string>
<string name="button_yes_quit">Da, izlaz</string>
<string name="settings_item_image_source_path">Spremite slike u određene poddirektorije</string>
<string name="settings_subtitle_image_source_path">upotrijebite ručku zajednice ili korisnika u stazama</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">Vannak nem mentett módosítások. Biztosan kilép?</string>
<string name="button_no_stay">Nem, maradj itt</string>
<string name="button_yes_quit">Igen, kilép</string>
<string name="settings_item_image_source_path">Mentse el a képeket meghatározott alkönyvtárakba</string>
<string name="settings_subtitle_image_source_path">közösségi vagy felhasználói fogantyú használata az elérési utakban</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">Ci sono modifiche non salvate. Sei sicuro di voler uscire?</string>
<string name="button_no_stay">No, resta qui</string>
<string name="button_yes_quit">Sì, esci</string>
<string name="settings_item_image_source_path">Salva le immagini in sottodirectory specifiche</string>
<string name="settings_subtitle_image_source_path">utilizza handle comunità o utente nei percorsi</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">Yra neišsaugotų pakeitimų. Ar tikrai norite išeiti?</string>
<string name="button_no_stay">Ne, pasilik čia</string>
<string name="button_yes_quit">Taip, išeiti</string>
<string name="settings_item_image_source_path">Išsaugokite vaizdus tam tikruose pakatalogiuose</string>
<string name="settings_subtitle_image_source_path">keliuose naudokite bendruomenės arba vartotojo rankenėlę</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">Ir nesaglabātas izmaiņas. Vai tiešām vēlaties iziet?</string>
<string name="button_no_stay">Nē, paliec šeit</string>
<string name="button_yes_quit">Jā, iziet</string>
<string name="settings_item_image_source_path">Saglabājiet attēlus noteiktos apakšdirektorijos</string>
<string name="settings_subtitle_image_source_path">ceļos izmantojiet kopienu vai lietotāja rokturi</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">Hemm bidliet mhux salvati, żgur li trid toħroġ?</string>
<string name="button_no_stay">Le, oqgħod hawn</string>
<string name="button_yes_quit">Iva, ħruġ</string>
<string name="settings_item_image_source_path">Ħlief immaġini f\'sub-direttorji speċifiċi</string>
<string name="settings_subtitle_image_source_path">uża l-komunità jew il-manku tal-utent fil-mogħdijiet</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">Er zijn niet-opgeslagen wijzigingen. Weet u zeker dat u wilt afsluiten?</string>
<string name="button_no_stay">Nee, blijf hier</string>
<string name="button_yes_quit">Ja, vertrek</string>
<string name="settings_item_image_source_path">Bewaar afbeeldingen in specifieke submappen</string>
<string name="settings_subtitle_image_source_path">gebruik community- of gebruikershandle in paden</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">Det er ulagrede endringer, er du sikker på at du vil avslutte?</string>
<string name="button_no_stay">Nei, bli her</string>
<string name="button_yes_quit">Ja, gå ut</string>
<string name="settings_item_image_source_path">Lagre bilder i bestemte underkataloger</string>
<string name="settings_subtitle_image_source_path">bruk fellesskap eller brukerhåndtak i baner</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">Istnieją niezapisane zmiany. Czy na pewno chcesz wyjść?</string>
<string name="button_no_stay">Nie, zostań tutaj</string>
<string name="button_yes_quit">Tak, wyjdź</string>
<string name="settings_item_image_source_path">Zapisuj obrazy w określonych podkatalogach</string>
<string name="settings_subtitle_image_source_path">użyj uchwytu społeczności lub użytkownika w ścieżkach</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">Existem alterações não salvas. Tem certeza de que deseja sair?</string>
<string name="button_no_stay">Não, ficar aqui</string>
<string name="button_yes_quit">Sim, sair</string>
<string name="settings_item_image_source_path">Salve imagens em subdiretórios específicos</string>
<string name="settings_subtitle_image_source_path">use comunidade ou identificador de usuário em caminhos</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">Existem alterações não salvas. Tem certeza de que deseja sair?</string>
<string name="button_no_stay">Não, ficar aqui</string>
<string name="button_yes_quit">Sim, sair</string>
<string name="settings_item_image_source_path">Salve imagens em subdiretórios específicos</string>
<string name="settings_subtitle_image_source_path">use comunidade ou identificador de usuário em caminhos</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">Există modificări nesalvate, ești sigur că vrei să ieși?</string>
<string name="button_no_stay">Nu, stai aici</string>
<string name="button_yes_quit">Da, ieși</string>
<string name="settings_item_image_source_path">Salvă imaginile în anumite subdirectoare</string>
<string name="settings_subtitle_image_source_path">utilizează comunitatea sau identificatorul utilizatorului în căi</string>
</resources>

View File

@ -339,4 +339,6 @@
<string name="message_unsaved_changes">Есть несохраненные изменения. Вы уверены, что хотите выйти?</string>
<string name="button_no_stay">Нет, оставайся здесь</string>
<string name="button_yes_quit">Да, выход</string>
<string name="settings_item_image_source_path">Сохраняйте изображения в определенных подкаталогах.</string>
<string name="settings_subtitle_image_source_path">используйте дескриптор сообщества или пользователя в путях</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">Det finns osparade ändringar, är du säker på att du vill avsluta?</string>
<string name="button_no_stay">Nej, stanna här</string>
<string name="button_yes_quit">Ja, avsluta</string>
<string name="settings_item_image_source_path">Spara bilder i specifika underkataloger</string>
<string name="settings_subtitle_image_source_path">använd gemenskap eller användarhandtag i sökvägar</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">Existujú neuložené zmeny. Naozaj chcete skončiť?</string>
<string name="button_no_stay">Nie, zostaň tu</string>
<string name="button_yes_quit">Áno, odísť</string>
<string name="settings_item_image_source_path">Uložte obrázky do konkrétnych podadresárov</string>
<string name="settings_subtitle_image_source_path">použite komunitný alebo užívateľský popisovač v cestách</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">Obstajajo neshranjene spremembe. Ste prepričani, da želite zapustiti?</string>
<string name="button_no_stay">Ne, ostani tukaj</string>
<string name="button_yes_quit">Ja, izhod</string>
<string name="settings_item_image_source_path">Shranite slike v določene podimenike</string>
<string name="settings_subtitle_image_source_path">uporabite ročaj skupnosti ali uporabnika v poteh</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">Ka ndryshime të paruajtura, je i sigurt që dëshiron të dalësh?</string>
<string name="button_no_stay">Jo, rri këtu</string>
<string name="button_yes_quit">Po, dil</string>
<string name="settings_item_image_source_path">Ruani imazhet në nën-drejtori të veçanta</string>
<string name="settings_subtitle_image_source_path">përdorni bashkësinë ose dorezën e përdoruesit në shtigje</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">sina ante e sona, taso sina awen ala e ona. sina wile ala wile tawa weka?</string>
<string name="button_no_stay">o awen</string>
<string name="button_yes_quit">o tawa weka</string>
<string name="settings_item_image_source_path">o awen e sitelen lon poki pi nimi lili</string>
<string name="settings_subtitle_image_source_path">o kepeken e nimi kulupu/nimi jan lon linja</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">Kaydedilmemiş değişiklikler var, çıkmak istediğinizden emin misiniz?</string>
<string name="button_no_stay">Hayır, burada kal</string>
<string name="button_yes_quit">Evet, çık</string>
<string name="settings_item_image_source_path">Görüntüleri belirli alt dizinlere kaydedin</string>
<string name="settings_subtitle_image_source_path">yollarda topluluk veya kullanıcı tanıtıcısını kullanın</string>
</resources>

View File

@ -339,4 +339,6 @@
<string name="message_unsaved_changes">Є незбережені зміни. Ви впевнені, що бажаєте вийти?</string>
<string name="button_no_stay">Ні, залишайся тут</string>
<string name="button_yes_quit">Так, вихід</string>
<string name="settings_item_image_source_path">Зберігайте зображення в певних підкаталогах</string>
<string name="settings_subtitle_image_source_path">використовуйте дескриптор спільноти або користувача в шляхах</string>
</resources>

View File

@ -340,4 +340,6 @@
<string name="message_unsaved_changes">There are unsaved changes, are you sure you want to quit?</string>
<string name="button_no_stay">No, stay here</string>
<string name="button_yes_quit">Yes, quit</string>
<string name="settings_item_image_source_path">Save images in specific sub-directories</string>
<string name="settings_subtitle_image_source_path">use community or user handle in paths</string>
</resources>

View File

@ -53,4 +53,5 @@ data class SettingsModel(
val showScores: Boolean = true,
val preferUserNicknames: Boolean = true,
val commentBarThickness: Int = 1,
val imageSourcePath: Boolean = false,
)

View File

@ -56,6 +56,7 @@ private object KeyStoreKeys {
const val SHOW_SCORES = "showScores"
const val PREFER_USER_NICKNAMES = "preferUserNicknames"
const val COMMENT_BAR_THICKNESS = "commentBarThickness"
const val IMAGE_SOURCE_PATH = "imageSourcePath"
}
internal class DefaultSettingsRepository(
@ -125,7 +126,8 @@ internal class DefaultSettingsRepository(
opaqueSystemBars = if (settings.opaqueSystemBars) 1L else 0L,
showScores = if (settings.showScores) 1L else 0L,
preferUserNicknames = if (settings.preferUserNicknames) 1L else 0L,
commentBarThickness = settings.commentBarThickness.toLong()
commentBarThickness = settings.commentBarThickness.toLong(),
imageSourcePath = if (settings.imageSourcePath) 1L else 0L,
)
}
@ -180,6 +182,7 @@ internal class DefaultSettingsRepository(
showScores = keyStore[KeyStoreKeys.SHOW_SCORES, true],
preferUserNicknames = keyStore[KeyStoreKeys.PREFER_USER_NICKNAMES, true],
commentBarThickness = keyStore[KeyStoreKeys.COMMENT_BAR_THICKNESS, 1],
imageSourcePath = keyStore[KeyStoreKeys.IMAGE_SOURCE_PATH, false],
)
} else {
val entity = db.settingsQueries.getBy(accountId).executeAsOneOrNull()
@ -280,6 +283,7 @@ internal class DefaultSettingsRepository(
keyStore.save(KeyStoreKeys.SHOW_SCORES, settings.showScores)
keyStore.save(KeyStoreKeys.PREFER_USER_NICKNAMES, settings.preferUserNicknames)
keyStore.save(KeyStoreKeys.COMMENT_BAR_THICKNESS, settings.commentBarThickness)
keyStore.save(KeyStoreKeys.IMAGE_SOURCE_PATH, settings.imageSourcePath)
} else {
db.settingsQueries.update(
theme = settings.theme?.toLong(),
@ -338,6 +342,7 @@ internal class DefaultSettingsRepository(
showScores = if (settings.showScores) 1L else 0L,
preferUserNicknames = if (settings.preferUserNicknames) 1L else 0L,
commentBarThickness = settings.commentBarThickness.toLong(),
imageSourcePath = if (settings.imageSourcePath) 1L else 0L,
)
}
}
@ -412,4 +417,5 @@ private fun GetBy.toModel() = SettingsModel(
showScores = showScores == 1L,
preferUserNicknames = preferUserNicknames == 1L,
commentBarThickness = commentBarThickness.toInt(),
imageSourcePath = imageSourcePath == 1L,
)

View File

@ -50,6 +50,7 @@ CREATE TABLE SettingsEntity (
showScores INTEGER NOT NULL DEFAULT 1,
preferUserNicknames INTEGER NOT NULL DEFAULT 1,
commentBarThickness INTEGER NOT NULL DEFAULT 0,
imageSourcePath INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY (account_id) REFERENCES AccountEntity(id) ON DELETE CASCADE,
UNIQUE(account_id)
);
@ -105,6 +106,7 @@ INSERT OR IGNORE INTO SettingsEntity (
showScores,
preferUserNicknames,
commentBarThickness,
imageSourcePath,
account_id
) VALUES (
?,
@ -156,6 +158,7 @@ INSERT OR IGNORE INTO SettingsEntity (
?,
?,
?,
?,
?
);
@ -209,7 +212,8 @@ SET theme = ?,
opaqueSystemBars = ?,
showScores = ?,
preferUserNicknames = ?,
commentBarThickness = ?
commentBarThickness = ?,
imageSourcePath = ?
WHERE account_id = ?;
getBy:
@ -263,6 +267,7 @@ SELECT
opaqueSystemBars,
showScores,
preferUserNicknames,
commentBarThickness
commentBarThickness,
imageSourcePath
FROM SettingsEntity
WHERE account_id = ?;

View File

@ -0,0 +1,2 @@
ALTER TABLE SettingsEntity
ADD COLUMN imageSourcePath INTEGER NOT NULL DEFAULT 0;

View File

@ -3,7 +3,7 @@ package com.github.diegoberaldin.raccoonforlemmy.core.utils.gallery
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Environment
import android.os.ParcelFileDescriptor
import android.provider.MediaStore
import androidx.activity.compose.rememberLauncherForActivityResult
@ -17,25 +17,32 @@ import kotlinx.coroutines.launch
import org.koin.dsl.module
import org.koin.java.KoinJavaComponent.inject
private const val DEFAULT_BASE_PATH = "RaccoonForLemmy"
class DefaultGalleryHelper(
private val context: Context,
) : GalleryHelper {
override fun saveToGallery(bytes: ByteArray, name: String) {
val resolver = context.applicationContext.contentResolver
override val supportsCustomPath: Boolean = true
val collection = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
} else {
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
override fun saveToGallery(bytes: ByteArray, name: String, additionalPathSegment: String?) {
val relativePath = buildString {
append(Environment.DIRECTORY_PICTURES)
append("/")
append(DEFAULT_BASE_PATH)
if (!additionalPathSegment.isNullOrEmpty()) {
append("/")
append(additionalPathSegment)
}
}
val resolver = context.applicationContext.contentResolver
val details = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, name)
put(MediaStore.Images.Media.IS_PENDING, 1)
put(MediaStore.Images.Media.RELATIVE_PATH, relativePath)
}
val uri = resolver.insert(collection, details)
val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, details)
if (uri != null) {
resolver.openFileDescriptor(uri, "w", null).use { pfd ->
ParcelFileDescriptor.AutoCloseOutputStream(pfd).use {

View File

@ -7,7 +7,10 @@ import org.koin.core.module.Module
@Stable
interface GalleryHelper {
fun saveToGallery(bytes: ByteArray, name: String)
val supportsCustomPath: Boolean
fun saveToGallery(bytes: ByteArray, name: String, additionalPathSegment: String? = null)
@Composable
fun getImageFromGallery(result: (ByteArray) -> Unit)

View File

@ -42,8 +42,10 @@ fun ImageBytes.toByteArray(): ByteArray = ByteArray(this@toByteArray.length.toIn
class DefaultGalleryHelper : GalleryHelper {
override val supportsCustomPath: Boolean = false
@OptIn(ExperimentalForeignApi::class)
override fun saveToGallery(bytes: ByteArray, name: String) {
override fun saveToGallery(bytes: ByteArray, name: String, additionalPathSegment: String?) {
val image = UIImage(bytes.toImageBytes())
UIImageWriteToSavedPhotosAlbum(image, null, null, null)
}

View File

@ -88,6 +88,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallb
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SearchResult
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SearchResultType
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.readableHandle
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toInt
import com.github.diegoberaldin.raccoonforlemmy.unit.web.WebViewScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.zoomableimage.ZoomableImageScreen
@ -504,7 +505,10 @@ class ExploreScreen : Screen {
},
onOpenImage = rememberCallbackArgs { url ->
navigationCoordinator.pushScreen(
ZoomableImageScreen(url),
ZoomableImageScreen(
url = url,
source = result.model.community?.readableHandle.orEmpty(),
),
)
},
onOpenPost = rememberCallbackArgs { post, instance ->

View File

@ -6,8 +6,8 @@ import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
interface AdvancedSettingsMviModel :
MviModel<AdvancedSettingsMviModel.Intent, AdvancedSettingsMviModel.UiState, AdvancedSettingsMviModel.Effect>,
ScreenModel {
ScreenModel,
MviModel<AdvancedSettingsMviModel.Intent, AdvancedSettingsMviModel.UiState, AdvancedSettingsMviModel.Effect> {
sealed interface Intent {
data class ChangeEnableDoubleTapAction(val value: Boolean) : Intent
@ -19,6 +19,7 @@ interface AdvancedSettingsMviModel :
data class ChangeSearchPostTitleOnly(val value: Boolean) : Intent
data class ChangeEdgeToEdge(val value: Boolean) : Intent
data class ChangeInfiniteScrollDisabled(val value: Boolean) : Intent
data class ChangeImageSourcePath(val value: Boolean) : Intent
}
data class UiState(
@ -36,6 +37,7 @@ interface AdvancedSettingsMviModel :
val edgeToEdge: Boolean = true,
val infiniteScrollDisabled: Boolean = false,
val opaqueSystemBars: Boolean = false,
val imageSourcePath: Boolean = false,
)
sealed interface Effect

View File

@ -121,13 +121,25 @@ class AdvancedSettingsScreen : Screen {
)
}
// image source path
SettingsSwitchRow(
title = LocalXmlStrings.current.settingsItemImageSourcePath,
subtitle = LocalXmlStrings.current.settingsSubtitleImageSourcePath,
value = uiState.imageSourcePath,
onValueChanged = rememberCallbackArgs(model) { value ->
model.reduce(
AdvancedSettingsMviModel.Intent.ChangeImageSourcePath(value),
)
},
)
// navigation bar titles
SettingsSwitchRow(
title = LocalXmlStrings.current.settingsNavigationBarTitlesVisible,
value = uiState.navBarTitlesVisible,
onValueChanged = rememberCallbackArgs(model) { value ->
model.reduce(
AdvancedSettingsMviModel.Intent.ChangeNavBarTitlesVisible(value)
AdvancedSettingsMviModel.Intent.ChangeNavBarTitlesVisible(value),
)
},
)

View File

@ -75,8 +75,8 @@ class AdvancedSettingsViewModel(
edgeToEdge = settings.edgeToEdge,
infiniteScrollDisabled = !settings.infiniteScrollEnabled,
opaqueSystemBars = settings.opaqueSystemBars,
)
imageSourcePath = settings.imageSourcePath,
)
}
}
@ -107,6 +107,8 @@ class AdvancedSettingsViewModel(
is AdvancedSettingsMviModel.Intent.ChangeEdgeToEdge -> changeEdgeToEdge(intent.value)
is AdvancedSettingsMviModel.Intent.ChangeInfiniteScrollDisabled ->
changeInfiniteScrollDisabled(intent.value)
is AdvancedSettingsMviModel.Intent.ChangeImageSourcePath -> changeImageSourcePath(intent.value)
}
}
@ -245,6 +247,16 @@ class AdvancedSettingsViewModel(
}
}
private fun changeImageSourcePath(value: Boolean) {
updateState { it.copy(imageSourcePath = value) }
scope?.launch(Dispatchers.IO) {
val settings = settingsRepository.currentSettings.value.copy(
imageSourcePath = value
)
saveSettings(settings)
}
}
private suspend fun saveSettings(settings: SettingsModel) {
val accountId = accountRepository.getActive()?.id
settingsRepository.updateSettings(settings, accountId)

View File

@ -68,6 +68,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallb
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallbackArgs
import com.github.diegoberaldin.raccoonforlemmy.core.utils.gallery.getGalleryHelper
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PrivateMessageModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.readableHandle
import com.github.diegoberaldin.raccoonforlemmy.unit.chat.components.MessageCard
import com.github.diegoberaldin.raccoonforlemmy.unit.chat.components.MessageCardPlaceholder
import com.github.diegoberaldin.raccoonforlemmy.unit.rawcontent.RawContentDialog
@ -250,7 +251,12 @@ class InboxChatScreen(
content = content,
date = date,
onOpenImage = rememberCallbackArgs { url ->
navigationCoordinator.pushScreen(ZoomableImageScreen(url))
navigationCoordinator.pushScreen(
ZoomableImageScreen(
url = url,
source = message.creator?.readableHandle.orEmpty(),
)
)
},
onOpenCommunity = rememberCallbackArgs { community, instance ->
detailOpener.openCommunityDetail(

View File

@ -547,7 +547,12 @@ class CommunityDetailScreen(
community = uiState.community,
autoLoadImages = uiState.autoLoadImages,
onOpenImage = rememberCallbackArgs { url ->
navigationCoordinator.pushScreen(ZoomableImageScreen(url))
navigationCoordinator.pushScreen(
ZoomableImageScreen(
url = url,
source = uiState.community.readableHandle,
)
)
},
)
}
@ -757,7 +762,10 @@ class CommunityDetailScreen(
)
)
navigationCoordinator.pushScreen(
ZoomableImageScreen(url),
ZoomableImageScreen(
url = url,
source = uiState.community.readableHandle,
),
)
},
options = buildList {

View File

@ -219,7 +219,12 @@ class CommunityInfoScreen(
navigationCoordinator.hideBottomSheet()
scope.launch {
delay(100)
navigationCoordinator.pushScreen(ZoomableImageScreen(url))
navigationCoordinator.pushScreen(
ZoomableImageScreen(
url = url,
source = uiState.community.readableHandle,
)
)
}
},
onOpenCommunity = { community, instance ->

View File

@ -55,6 +55,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.navigation.di.getNavigation
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.ActionOnSwipe
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallback
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallbackArgs
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.readableHandle
import com.github.diegoberaldin.raccoonforlemmy.unit.zoomableimage.ZoomableImageScreen
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@ -242,7 +243,12 @@ class InboxMentionsScreen : Tab {
detailOpener.openCommunityDetail(community = community)
},
onImageClick = rememberCallbackArgs { url ->
navigationCoordinator.pushScreen(ZoomableImageScreen(url))
navigationCoordinator.pushScreen(
ZoomableImageScreen(
url = url,
source = mention.post.community?.readableHandle.orEmpty(),
)
)
},
onUpVote = rememberCallbackArgs(model) { _ ->
model.reduce(InboxMentionsMviModel.Intent.UpVoteComment(mention.id))

View File

@ -67,6 +67,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallback
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallbackArgs
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.readableHandle
import com.github.diegoberaldin.raccoonforlemmy.unit.ban.BanUserScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.moddedcontents.comments.components.ModdedCommentPlaceholder
import com.github.diegoberaldin.raccoonforlemmy.unit.rawcontent.RawContentDialog
@ -321,7 +322,10 @@ class ModdedPostsScreen : Screen {
},
onOpenImage = rememberCallbackArgs(model, post) { url ->
navigationCoordinator.pushScreen(
ZoomableImageScreen(url)
ZoomableImageScreen(
url = url,
source = post.community?.readableHandle.orEmpty(),
)
)
},
options = buildList {

View File

@ -85,6 +85,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallback
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallbackArgs
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.getAdditionalLabel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.readableHandle
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toIcon
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toInt
import com.github.diegoberaldin.raccoonforlemmy.unit.createreport.CreateReportScreen
@ -444,7 +445,12 @@ class MultiCommunityScreen(
},
onOpenImage = rememberCallbackArgs { url ->
model.reduce(MultiCommunityMviModel.Intent.MarkAsRead(post.id))
navigationCoordinator.pushScreen(ZoomableImageScreen(url))
navigationCoordinator.pushScreen(
ZoomableImageScreen(
url = url,
source = post.community?.readableHandle.orEmpty(),
)
)
},
options = buildList {
add(

View File

@ -65,6 +65,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallb
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallbackArgs
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommentModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.readableHandle
import com.github.diegoberaldin.raccoonforlemmy.unit.drafts.DraftsScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.managesubscriptions.ManageSubscriptionsScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.moddedcontents.comments.ModdedCommentsScreen
@ -178,7 +179,12 @@ object ProfileLoggedScreen : Tab {
user = user,
autoLoadImages = uiState.autoLoadImages,
onOpenImage = rememberCallbackArgs { url ->
navigationCoordinator.pushScreen(ZoomableImageScreen(url))
navigationCoordinator.pushScreen(
ZoomableImageScreen(
url = url,
source = uiState.user?.readableHandle.orEmpty(),
)
)
},
)
}
@ -276,7 +282,10 @@ object ProfileLoggedScreen : Tab {
},
onOpenImage = rememberCallbackArgs { url ->
navigationCoordinator.pushScreen(
ZoomableImageScreen(url),
ZoomableImageScreen(
url = url,
source = post.community?.readableHandle.orEmpty(),
),
)
},
onUpVote = rememberCallback(model) {
@ -406,7 +415,12 @@ object ProfileLoggedScreen : Tab {
hideAuthor = true,
hideIndent = true,
onImageClick = rememberCallbackArgs { url ->
navigationCoordinator.pushScreen(ZoomableImageScreen(url))
navigationCoordinator.pushScreen(
ZoomableImageScreen(
url = url,
source = comment.community?.readableHandle.orEmpty(),
)
)
},
onOpenCommunity = rememberCallbackArgs { community, instance ->
detailOpener.openCommunityDetail(community, instance)

View File

@ -101,6 +101,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallb
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommentModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.containsId
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.readableHandle
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.readableName
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toIcon
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toInt
@ -508,7 +509,10 @@ class PostDetailScreen(
},
onOpenImage = rememberCallbackArgs { url ->
navigationCoordinator.pushScreen(
ZoomableImageScreen(url),
ZoomableImageScreen(
url = url,
source = uiState.post.community?.readableHandle.orEmpty(),
),
)
},
)
@ -789,7 +793,10 @@ class PostDetailScreen(
},
onImageClick = rememberCallbackArgs { url ->
navigationCoordinator.pushScreen(
ZoomableImageScreen(url)
ZoomableImageScreen(
url = url,
source = uiState.post.community?.readableHandle.orEmpty(),
)
)
},
options = buildList {

View File

@ -87,6 +87,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallb
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallbackArgs
import com.github.diegoberaldin.raccoonforlemmy.core.utils.keepscreenon.rememberKeepScreenOn
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.readableHandle
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.readableName
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toInt
import com.github.diegoberaldin.raccoonforlemmy.unit.createreport.CreateReportScreen
@ -324,7 +325,10 @@ class PostListScreen : Screen {
items(
items = uiState.posts,
// isLogged is added to the key to force swipe action refresh
key = { it.id.toString() + (it.updateDate ?: it.publishDate) + uiState.isLogged },
key = {
it.id.toString() + (it.updateDate
?: it.publishDate) + uiState.isLogged
},
) { post ->
LaunchedEffect(post.id) {
if (settings.markAsReadWhileScrolling && !post.read) {
@ -501,7 +505,10 @@ class PostListScreen : Screen {
onOpenImage = rememberCallbackArgs(model, post) { url ->
model.reduce(PostListMviModel.Intent.MarkAsRead(post.id))
navigationCoordinator.pushScreen(
ZoomableImageScreen(url)
ZoomableImageScreen(
url = url,
source = post.community?.readableHandle.orEmpty(),
)
)
},
options = buildList {

View File

@ -56,6 +56,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.navigation.di.getNavigation
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.ActionOnSwipe
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallback
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallbackArgs
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.readableHandle
import com.github.diegoberaldin.raccoonforlemmy.unit.zoomableimage.ZoomableImageScreen
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@ -238,7 +239,12 @@ class InboxRepliesScreen : Tab {
detailOpener.openCommunityDetail(community = community)
},
onImageClick = rememberCallbackArgs { url ->
navigationCoordinator.pushScreen(ZoomableImageScreen(url))
navigationCoordinator.pushScreen(
ZoomableImageScreen(
url = url,
source = reply.post.community?.readableHandle.orEmpty(),
)
)
},
onUpVote = rememberCallbackArgs(model) { _ ->
model.reduce(InboxRepliesMviModel.Intent.UpVoteComment(reply.id))

View File

@ -68,6 +68,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallb
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallbackArgs
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommentModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.readableHandle
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toIcon
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toInt
import com.github.diegoberaldin.raccoonforlemmy.unit.createreport.CreateReportScreen
@ -264,7 +265,10 @@ class SavedItemsScreen : Screen {
},
onOpenImage = rememberCallbackArgs { url ->
navigatorCoordinator.pushScreen(
ZoomableImageScreen(url),
ZoomableImageScreen(
url = url,
source = post.community?.readableHandle.orEmpty(),
),
)
},
options = buildList {
@ -355,7 +359,12 @@ class SavedItemsScreen : Screen {
)
},
onImageClick = rememberCallbackArgs { url ->
navigationCoordinator.pushScreen(ZoomableImageScreen(url))
navigationCoordinator.pushScreen(
ZoomableImageScreen(
url = url,
source = comment.community?.readableHandle.orEmpty(),
)
)
},
onUpVote = {
model.reduce(

View File

@ -423,7 +423,8 @@ class UserDetailScreen(
onOpenImage = rememberCallbackArgs { url ->
navigationCoordinator.pushScreen(
ZoomableImageScreen(
url
url = url,
source = uiState.user?.readableHandle.orEmpty(),
)
)
},
@ -655,7 +656,10 @@ class UserDetailScreen(
},
onOpenImage = rememberCallbackArgs { url ->
navigationCoordinator.pushScreen(
ZoomableImageScreen(url),
ZoomableImageScreen(
url = url,
source = post.community?.readableHandle.orEmpty(),
),
)
},
options = buildList {
@ -884,7 +888,10 @@ class UserDetailScreen(
},
onImageClick = rememberCallbackArgs { url ->
navigationCoordinator.pushScreen(
ZoomableImageScreen(url)
ZoomableImageScreen(
url = url,
source = comment.community?.readableHandle.orEmpty(),
)
)
},
onDoubleClick = if (!uiState.doubleTapActionEnabled) {

View File

@ -46,6 +46,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.navigation.di.getNavigation
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallbackArgs
import com.github.diegoberaldin.raccoonforlemmy.core.utils.datetime.prettifyDate
import com.github.diegoberaldin.raccoonforlemmy.core.utils.getPrettyNumber
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.readableHandle
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.readableName
import com.github.diegoberaldin.raccoonforlemmy.unit.userinfo.components.ModeratedCommunityCell
import com.github.diegoberaldin.raccoonforlemmy.unit.web.WebViewScreen
@ -173,7 +174,8 @@ class UserInfoScreen(
delay(100)
navigationCoordinator.pushScreen(
ZoomableImageScreen(
url
url = url,
source = uiState.user.readableHandle,
)
)
}

View File

@ -6,11 +6,14 @@ import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
@Stable
interface ZoomableImageMviModel :
MviModel<ZoomableImageMviModel.Intent, ZoomableImageMviModel.UiState, ZoomableImageMviModel.Effect>,
ScreenModel {
ScreenModel,
MviModel<ZoomableImageMviModel.Intent, ZoomableImageMviModel.UiState, ZoomableImageMviModel.Effect> {
sealed interface Intent {
data class Share(val url: String) : Intent
data class SaveToGallery(val url: String) : Intent
data class SaveToGallery(
val source: String,
val url: String,
) : Intent
}
data class UiState(

View File

@ -43,6 +43,7 @@ import kotlinx.coroutines.flow.onEach
class ZoomableImageScreen(
private val url: String,
private val source: String = "",
) : Screen {
@OptIn(ExperimentalMaterial3Api::class)
@ -95,7 +96,12 @@ class ZoomableImageScreen(
Icon(
modifier = Modifier.onClick(
onClick = rememberCallback {
model.reduce(ZoomableImageMviModel.Intent.SaveToGallery(url))
model.reduce(
ZoomableImageMviModel.Intent.SaveToGallery(
url = url,
source = source,
)
)
},
),
imageVector = Icons.Default.Download,

View File

@ -38,11 +38,15 @@ class ZoomableImageViewModel(
}
}
is ZoomableImageMviModel.Intent.SaveToGallery -> downloadAndSave(intent.url)
is ZoomableImageMviModel.Intent.SaveToGallery -> downloadAndSave(
folder = intent.source,
url = intent.url,
)
}
}
private fun downloadAndSave(url: String) {
private fun downloadAndSave(url: String, folder: String) {
val imageSourcePath = settingsRepository.currentSettings.value.imageSourcePath
scope?.launch(Dispatchers.IO) {
updateState { it.copy(loading = true) }
try {
@ -51,7 +55,11 @@ class ZoomableImageViewModel(
val idx = s.lastIndexOf(".").takeIf { it >= 0 } ?: s.length
s.substring(idx).takeIf { it.isNotEmpty() } ?: ".jpeg"
}
galleryHelper.saveToGallery(bytes, "${epochMillis()}.$extension")
galleryHelper.saveToGallery(
bytes = bytes,
name = "${epochMillis()}$extension",
additionalPathSegment = folder.takeIf { imageSourcePath },
)
emitEffect(ZoomableImageMviModel.Effect.ShareSuccess)
} catch (e: Throwable) {
e.printStackTrace()