feat: Add option to save attachments to per-account folders (#945)
The existing code downloaded any attachments to the user's "Downloads" folder. If the user is logged in with several accounts these downloads will be mixed up together. Fix this by adding a new preference that allows the user to specify the downloads should be placed in a sub-folder per account, named after the account. To do this: - Add an interface for enums that can be used as preferences, with properties for the string resource to display and the value to store. - Add `EnumListPreference`, a `ListPreference` that allows the user to choose between different enum values. - Add a `DownloadLocation` enum and preference key so the user can choose the location. - Add a `core.domain` module, with a use case for downloading URLs that respect's the user's download preference. Use this use-case everywhere that files are currently downloaded. Fixes #938
This commit is contained in:
parent
91284ffad1
commit
85ab714ec1
|
@ -127,6 +127,7 @@ dependencies {
|
|||
implementation(projects.core.data)
|
||||
implementation(projects.core.database)
|
||||
implementation(projects.core.designsystem)
|
||||
implementation(projects.core.domain)
|
||||
implementation(projects.core.model)
|
||||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.network)
|
||||
|
|
|
@ -102,7 +102,7 @@
|
|||
errorLine2=" ^">
|
||||
<location
|
||||
file="src/main/java/app/pachli/components/compose/MediaUploader.kt"
|
||||
line="388"
|
||||
line="392"
|
||||
column="28"/>
|
||||
<location
|
||||
file="${:core:activity*buildDir}/generated/res/resValues/blueFdroid/debug/values/gradleResValues.xml"
|
||||
|
@ -716,6 +716,28 @@
|
|||
column="43"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="Typos"
|
||||
message=""Media" is a common misspelling; did you mean "Medier"?"
|
||||
errorLine1=" <string name="search_operator_attachment_all">Media ▾</string>"
|
||||
errorLine2=" ^">
|
||||
<location
|
||||
file="src/main/res/values-nb-rNO/strings.xml"
|
||||
line="736"
|
||||
column="51"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="Typos"
|
||||
message=""media" is a common misspelling; did you mean "medier"?"
|
||||
errorLine1=" <string name="search_operator_attachment_no_media_label">Ingen media</string>"
|
||||
errorLine2=" ^">
|
||||
<location
|
||||
file="src/main/res/values-nb-rNO/strings.xml"
|
||||
line="760"
|
||||
column="68"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ImpliedQuantity"
|
||||
message="The quantity `'one'` matches more than one specific number in this locale (0, 1), but the message did not include a formatting argument (such as `%d`). This is usually an internationalization error. See full issue explanation for more."
|
||||
|
|
|
@ -26,10 +26,8 @@ import android.content.ClipboardManager
|
|||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.transition.Transition
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
|
@ -50,6 +48,7 @@ import app.pachli.core.activity.extensions.startActivityWithDefaultTransition
|
|||
import app.pachli.core.common.extensions.hide
|
||||
import app.pachli.core.common.extensions.show
|
||||
import app.pachli.core.common.extensions.viewBinding
|
||||
import app.pachli.core.domain.DownloadUrlUseCase
|
||||
import app.pachli.core.navigation.AttachmentViewData
|
||||
import app.pachli.core.navigation.ViewMediaActivityIntent
|
||||
import app.pachli.core.navigation.ViewThreadActivityIntent
|
||||
|
@ -80,6 +79,9 @@ class ViewMediaActivity : BaseActivity(), MediaActionsListener {
|
|||
@Inject
|
||||
lateinit var okHttpClient: OkHttpClient
|
||||
|
||||
@Inject
|
||||
lateinit var downloadUrlUseCase: DownloadUrlUseCase
|
||||
|
||||
private val viewModel: ViewMediaViewModel by viewModels()
|
||||
|
||||
private val binding by viewBinding(ActivityViewMediaBinding::inflate)
|
||||
|
@ -224,13 +226,8 @@ class ViewMediaActivity : BaseActivity(), MediaActionsListener {
|
|||
|
||||
private fun downloadMedia() {
|
||||
val url = imageUrl ?: attachmentViewData!![binding.viewPager.currentItem].attachment.url
|
||||
val filename = Uri.parse(url).lastPathSegment
|
||||
Toast.makeText(applicationContext, resources.getString(R.string.download_image, filename), Toast.LENGTH_SHORT).show()
|
||||
|
||||
val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
val request = DownloadManager.Request(Uri.parse(url))
|
||||
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename)
|
||||
downloadManager.enqueue(request)
|
||||
Toast.makeText(applicationContext, resources.getString(R.string.download_image, url), Toast.LENGTH_SHORT).show()
|
||||
downloadUrlUseCase(url)
|
||||
}
|
||||
|
||||
private fun requestDownloadMedia() {
|
||||
|
|
|
@ -52,6 +52,7 @@ import app.pachli.core.designsystem.R as DR
|
|||
import app.pachli.core.network.model.Notification
|
||||
import app.pachli.core.preferences.AppTheme
|
||||
import app.pachli.core.preferences.AppTheme.Companion.APP_THEME_DEFAULT
|
||||
import app.pachli.core.preferences.DownloadLocation
|
||||
import app.pachli.core.preferences.PrefKeys
|
||||
import app.pachli.core.preferences.SharedPreferencesRepository
|
||||
import app.pachli.core.ui.extensions.await
|
||||
|
@ -60,6 +61,7 @@ import app.pachli.databinding.AccountNotificationDetailsListItemBinding
|
|||
import app.pachli.feature.about.asDdHhMmSs
|
||||
import app.pachli.feature.about.instantFormatter
|
||||
import app.pachli.settings.emojiPreference
|
||||
import app.pachli.settings.enumListPreference
|
||||
import app.pachli.settings.listPreference
|
||||
import app.pachli.settings.makePreferenceScreen
|
||||
import app.pachli.settings.preference
|
||||
|
@ -302,6 +304,15 @@ class PreferencesFragment : PreferenceFragmentCompat() {
|
|||
}
|
||||
}
|
||||
|
||||
preferenceCategory(app.pachli.core.preferences.R.string.pref_category_downloads) {
|
||||
enumListPreference<DownloadLocation> {
|
||||
setDefaultValue(DownloadLocation.DOWNLOADS)
|
||||
setTitle(app.pachli.core.preferences.R.string.pref_title_downloads)
|
||||
key = PrefKeys.DOWNLOAD_LOCATION
|
||||
icon = makeIcon(GoogleMaterial.Icon.gmd_file_download)
|
||||
}
|
||||
}
|
||||
|
||||
preferenceCategory(R.string.pref_title_edit_notification_settings) {
|
||||
val method = notificationMethod(context, accountManager)
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
package app.pachli.components.search.fragments
|
||||
|
||||
import android.Manifest
|
||||
import android.app.DownloadManager
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
|
@ -25,7 +24,6 @@ import android.content.Intent
|
|||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
|
@ -46,6 +44,7 @@ import app.pachli.core.activity.extensions.startActivityWithTransition
|
|||
import app.pachli.core.activity.openLink
|
||||
import app.pachli.core.data.repository.StatusDisplayOptionsRepository
|
||||
import app.pachli.core.database.model.AccountEntity
|
||||
import app.pachli.core.domain.DownloadUrlUseCase
|
||||
import app.pachli.core.navigation.AttachmentViewData
|
||||
import app.pachli.core.navigation.ComposeActivityIntent
|
||||
import app.pachli.core.navigation.ComposeActivityIntent.ComposeOptions
|
||||
|
@ -73,6 +72,9 @@ class SearchStatusesFragment : SearchFragment<StatusViewData>(), StatusActionLis
|
|||
@Inject
|
||||
lateinit var statusDisplayOptionsRepository: StatusDisplayOptionsRepository
|
||||
|
||||
@Inject
|
||||
lateinit var downloadUrlUseCase: DownloadUrlUseCase
|
||||
|
||||
override val data: Flow<PagingData<StatusViewData>>
|
||||
get() = viewModel.statusesFlow
|
||||
|
||||
|
@ -379,13 +381,7 @@ class SearchStatusesFragment : SearchFragment<StatusViewData>(), StatusActionLis
|
|||
private fun downloadAllMedia(status: Status) {
|
||||
Toast.makeText(context, R.string.downloading_media, Toast.LENGTH_SHORT).show()
|
||||
for ((_, url) in status.attachments) {
|
||||
val uri = Uri.parse(url)
|
||||
val filename = uri.lastPathSegment
|
||||
|
||||
val downloadManager = requireActivity().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
val request = DownloadManager.Request(uri)
|
||||
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename)
|
||||
downloadManager.enqueue(request)
|
||||
downloadUrlUseCase(url)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
package app.pachli.fragment
|
||||
|
||||
import android.Manifest
|
||||
import android.app.DownloadManager
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
|
@ -26,7 +25,6 @@ import android.content.pm.PackageManager
|
|||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
|
@ -50,6 +48,7 @@ import app.pachli.core.activity.openLink
|
|||
import app.pachli.core.data.repository.ServerRepository
|
||||
import app.pachli.core.database.model.AccountEntity
|
||||
import app.pachli.core.database.model.TranslationState
|
||||
import app.pachli.core.domain.DownloadUrlUseCase
|
||||
import app.pachli.core.navigation.AttachmentViewData
|
||||
import app.pachli.core.navigation.ComposeActivityIntent
|
||||
import app.pachli.core.navigation.ComposeActivityIntent.ComposeOptions
|
||||
|
@ -93,6 +92,9 @@ abstract class SFragment<T : IStatusViewData> : Fragment(), StatusActionListener
|
|||
@Inject
|
||||
lateinit var serverRepository: ServerRepository
|
||||
|
||||
@Inject
|
||||
lateinit var downloadUrlUseCase: DownloadUrlUseCase
|
||||
|
||||
private var serverCanTranslate = false
|
||||
|
||||
override fun startActivity(intent: Intent) {
|
||||
|
@ -542,16 +544,8 @@ abstract class SFragment<T : IStatusViewData> : Fragment(), StatusActionListener
|
|||
|
||||
private fun downloadAllMedia(status: Status) {
|
||||
Toast.makeText(context, R.string.downloading_media, Toast.LENGTH_SHORT).show()
|
||||
val downloadManager = requireActivity().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
|
||||
for ((_, url) in status.attachments) {
|
||||
val uri = Uri.parse(url)
|
||||
downloadManager.enqueue(
|
||||
DownloadManager.Request(uri).apply {
|
||||
setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, uri.lastPathSegment)
|
||||
},
|
||||
)
|
||||
}
|
||||
status.attachments.forEach { downloadUrlUseCase(it.url) }
|
||||
}
|
||||
|
||||
private fun requestDownloadAllMedia(status: Status) {
|
||||
|
|
|
@ -13,6 +13,8 @@ import androidx.preference.PreferenceCategory
|
|||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.PreferenceScreen
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import app.pachli.core.preferences.PreferenceEnum
|
||||
import app.pachli.core.ui.EnumListPreference
|
||||
import app.pachli.view.SliderPreference
|
||||
import de.c1710.filemojicompat_ui.views.picker.preference.EmojiPickerPreference
|
||||
|
||||
|
@ -35,6 +37,17 @@ inline fun PreferenceParent.listPreference(builder: ListPreference.() -> Unit):
|
|||
return pref
|
||||
}
|
||||
|
||||
inline fun <reified T> PreferenceParent.enumListPreference(
|
||||
builder: EnumListPreference<T>.() -> Unit,
|
||||
): EnumListPreference<T>
|
||||
where T : Enum<T>,
|
||||
T : PreferenceEnum {
|
||||
val pref = EnumListPreference<T>(context)
|
||||
builder(pref)
|
||||
addPref(pref)
|
||||
return pref
|
||||
}
|
||||
|
||||
inline fun <A> PreferenceParent.emojiPreference(
|
||||
activity: A,
|
||||
builder: EmojiPickerPreference.() -> Unit,
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2024 Pachli Association
|
||||
*
|
||||
* This file is a part of Pachli.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Pachli; if not,
|
||||
* see <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.pachli.android.library)
|
||||
alias(libs.plugins.pachli.android.hilt)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "app.pachli.core.domain"
|
||||
|
||||
defaultConfig {
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core.accounts)
|
||||
implementation(projects.core.preferences)
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<issues format="6" by="lint 8.5.2" type="baseline" client="gradle" dependencies="false" name="AGP (8.5.2)" variant="all" version="8.5.2">
|
||||
|
||||
</issues>
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright 2024 Pachli Association
|
||||
*
|
||||
* This file is a part of Pachli.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Pachli; if not,
|
||||
* see <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
package app.pachli.core.domain
|
||||
|
||||
import android.app.DownloadManager
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Environment
|
||||
import app.pachli.core.accounts.AccountManager
|
||||
import app.pachli.core.preferences.DownloadLocation
|
||||
import app.pachli.core.preferences.SharedPreferencesRepository
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Downloads a URL respecting the user's preferences.
|
||||
*
|
||||
* @see [invoke]
|
||||
*/
|
||||
class DownloadUrlUseCase @Inject constructor(
|
||||
@ApplicationContext val context: Context,
|
||||
private val sharedPreferencesRepository: SharedPreferencesRepository,
|
||||
private val accountManager: AccountManager,
|
||||
) {
|
||||
/**
|
||||
* Enqueues a [DownloadManager] request to download [url].
|
||||
*
|
||||
* The downloaded file is named after the URL's last path segment, and is
|
||||
* either saved to the "Downloads" directory, or a subdirectory named after
|
||||
* the user's account, depending on the app's preferences.
|
||||
*/
|
||||
operator fun invoke(url: String) {
|
||||
val uri = Uri.parse(url)
|
||||
val filename = uri.lastPathSegment ?: return
|
||||
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
val request = DownloadManager.Request(uri)
|
||||
|
||||
val locationPref = sharedPreferencesRepository.downloadLocation
|
||||
|
||||
val path = when (locationPref) {
|
||||
DownloadLocation.DOWNLOADS -> filename
|
||||
DownloadLocation.DOWNLOADS_PER_ACCOUNT -> {
|
||||
accountManager.activeAccount?.let {
|
||||
File(it.fullName, filename).toString()
|
||||
} ?: filename
|
||||
}
|
||||
}
|
||||
|
||||
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, path)
|
||||
downloadManager.enqueue(request)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright 2024 Pachli Association
|
||||
*
|
||||
* This file is a part of Pachli.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Pachli; if not,
|
||||
* see <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
package app.pachli.core.preferences
|
||||
|
||||
/** Where to save downloaded files. */
|
||||
enum class DownloadLocation(override val displayResource: Int, override val value: String? = null) :
|
||||
PreferenceEnum {
|
||||
/** Save to the root of the "Downloads" directory. */
|
||||
DOWNLOADS(R.string.download_location_downloads),
|
||||
|
||||
/** Save in per-account folders in the "Downloads" directory. */
|
||||
DOWNLOADS_PER_ACCOUNT(R.string.download_location_per_account),
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright 2024 Pachli Association
|
||||
*
|
||||
* This file is a part of Pachli.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Pachli; if not,
|
||||
* see <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
package app.pachli.core.preferences
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
|
||||
/**
|
||||
* Interface for enums that can be saved/restored from [SharedPreferencesRepository].
|
||||
*/
|
||||
interface PreferenceEnum {
|
||||
/** String resource for the enum's value. */
|
||||
@get:StringRes
|
||||
val displayResource: Int
|
||||
|
||||
/**
|
||||
* The value to persist in [SharedPreferencesRepository].
|
||||
*
|
||||
* If null the enum's [name][Enum.name] property is used.
|
||||
*/
|
||||
val value: String?
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* @return The enum identified by [s], or null if the enum does not have [s] as
|
||||
* a string representation.
|
||||
*/
|
||||
inline fun <reified T : Enum<T>> from(s: String?): T? {
|
||||
s ?: return null
|
||||
|
||||
return try {
|
||||
enumValueOf<T>(s)
|
||||
} catch (_: IllegalArgumentException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -137,6 +137,8 @@ object PrefKeys {
|
|||
*/
|
||||
const val USE_PREVIOUS_UNIFIED_PUSH_DISTRIBUTOR = "usePreviousUnifiedPushDistributor"
|
||||
|
||||
const val DOWNLOAD_LOCATION = "downloadLocation"
|
||||
|
||||
/** Keys that are no longer used (e.g., the preference has been removed */
|
||||
object Deprecated {
|
||||
// Empty at this time
|
||||
|
|
|
@ -5,3 +5,14 @@ import android.content.SharedPreferences
|
|||
fun SharedPreferences.getNonNullString(key: String, defValue: String): String {
|
||||
return this.getString(key, defValue) ?: defValue
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The enum for the preference at [key]. If there is no value for [key]
|
||||
* in preferences, or the value can not be converted to [E], then [defValue] is
|
||||
* returned.
|
||||
*/
|
||||
inline fun <reified E : Enum<E>> SharedPreferences.getEnum(key: String, defValue: E): E {
|
||||
val enumVal = getString(key, null) ?: return defValue
|
||||
|
||||
return PreferenceEnum.from<E>(enumVal) ?: defValue
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@ import javax.inject.Singleton
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* An implementation of [SharedPreferences] that exposes all changes to the
|
||||
|
@ -46,6 +45,10 @@ class SharedPreferencesRepository @Inject constructor(
|
|||
*/
|
||||
val changes = MutableSharedFlow<String?>()
|
||||
|
||||
/** Location of downloaded files. */
|
||||
val downloadLocation: DownloadLocation
|
||||
get() = getEnum(PrefKeys.DOWNLOAD_LOCATION, DownloadLocation.DOWNLOADS)
|
||||
|
||||
// Ensure the listener is retained during minification. If you do not do this the
|
||||
// field is removed and eventually garbage collected (because registering it as a
|
||||
// change listener does not create a strong reference to it) and then no more
|
||||
|
@ -57,7 +60,6 @@ class SharedPreferencesRepository @Inject constructor(
|
|||
}
|
||||
|
||||
init {
|
||||
Timber.d("Being created")
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2024 Pachli Association
|
||||
~
|
||||
~ This file is a part of Pachli.
|
||||
~
|
||||
~ This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
~ GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
~ License, or (at your option) any later version.
|
||||
~
|
||||
~ Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
~ the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
~ Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License along with Pachli; if not,
|
||||
~ see <http://www.gnu.org/licenses>.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<string name="pref_category_downloads">Downloads</string>
|
||||
<string name="pref_title_downloads">Download location</string>
|
||||
<string name="download_location_downloads">Downloads folder</string>
|
||||
<string name="download_location_per_account">Per-account folders, in Downloads folder</string>
|
||||
</resources>
|
|
@ -34,6 +34,8 @@ dependencies {
|
|||
implementation(projects.core.activity)
|
||||
implementation(projects.core.common)
|
||||
implementation(projects.core.designsystem)
|
||||
implementation(projects.core.preferences)
|
||||
?.because("PreferenceEnum types in EnumListPreference")
|
||||
|
||||
// Uses HttpException from Retrofit
|
||||
implementation(projects.core.network)
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright 2024 Pachli Association
|
||||
*
|
||||
* This file is a part of Pachli.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Pachli; if not,
|
||||
* see <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
package app.pachli.core.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.preference.ListPreference
|
||||
import app.pachli.core.preferences.PreferenceEnum
|
||||
|
||||
/**
|
||||
* Displays the different enums in a [PreferenceEnum], allowing the user to choose one
|
||||
* value.
|
||||
*
|
||||
* A [SummaryProvider][androidx.preference.Preference.SummaryProvider] is automatically
|
||||
* set to show the chosen value.
|
||||
*/
|
||||
class EnumListPreference<T> @JvmOverloads constructor(
|
||||
clazz: Class<T>,
|
||||
private val context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = androidx.preference.R.attr.dialogPreferenceStyle,
|
||||
defStyleRes: Int = android.R.attr.dialogPreferenceStyle,
|
||||
) : ListPreference(context, attrs, defStyleAttr, defStyleRes)
|
||||
where T : Enum<T>,
|
||||
T : PreferenceEnum {
|
||||
init {
|
||||
entries = clazz.enumConstants?.map { context.getString(it.displayResource) }?.toTypedArray<CharSequence>()
|
||||
?: emptyArray()
|
||||
entryValues = clazz.enumConstants?.map { it.value ?: it.name }?.toTypedArray<CharSequence>() ?: emptyArray()
|
||||
setSummaryProvider { entry }
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
"Do not use, call setDefaultValue with an enum",
|
||||
replaceWith = ReplaceWith("setDefaultValue(defaultValue)"),
|
||||
level = DeprecationLevel.ERROR,
|
||||
)
|
||||
override fun setDefaultValue(defaultValue: Any?) {
|
||||
throw IllegalStateException("Call setDefaultValue with an enum value")
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default value for this preference, which will be set either if persistence is off
|
||||
* or persistence is on and the preference is not found in the persistent storage.
|
||||
*
|
||||
* @param defaultValue The default value
|
||||
*/
|
||||
fun setDefaultValue(defaultValue: T) {
|
||||
super.setDefaultValue(defaultValue.value ?: defaultValue.name)
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Can't use reified types in a class constructor, but you can in inline
|
||||
// functions, so this helper supplies the correct type for the constructor's
|
||||
// first class parameter, making it more ergonomic to use this class.
|
||||
inline operator fun <reified T> invoke(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = androidx.preference.R.attr.dialogPreferenceStyle,
|
||||
defStyleRes: Int = android.R.attr.dialogPreferenceStyle,
|
||||
): EnumListPreference<T>
|
||||
where T : Enum<T>,
|
||||
T : PreferenceEnum = EnumListPreference(
|
||||
T::class.java,
|
||||
context,
|
||||
attrs,
|
||||
defStyleAttr,
|
||||
defStyleRes,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -56,6 +56,7 @@ include(":core:common")
|
|||
include(":core:data")
|
||||
include(":core:database")
|
||||
include(":core:designsystem")
|
||||
include(":core:domain")
|
||||
include(":core:model")
|
||||
include(":core:preferences")
|
||||
include(":core:navigation")
|
||||
|
|
Loading…
Reference in New Issue