feat: Add option to download media to per-sender directories (#954)
Update `DownloadUrlUseCase` with a parameter to specify the account that "owns" the media. This is either the account that posted the status, or the account being viewed (e.g., if downloading an account's header image). Add a new `DownloadLocation` enum constant to download to directories named after that account. Pass this information through at the call sites. Fixes #938
This commit is contained in:
parent
a90c7b387c
commit
90537da122
@ -89,6 +89,7 @@ class ViewMediaActivity : BaseActivity(), MediaActionsListener {
|
|||||||
val toolbar: View
|
val toolbar: View
|
||||||
get() = binding.toolbar
|
get() = binding.toolbar
|
||||||
|
|
||||||
|
private lateinit var owningUsername: String
|
||||||
private var attachmentViewData: List<AttachmentViewData>? = null
|
private var attachmentViewData: List<AttachmentViewData>? = null
|
||||||
private var imageUrl: String? = null
|
private var imageUrl: String? = null
|
||||||
|
|
||||||
@ -106,6 +107,7 @@ class ViewMediaActivity : BaseActivity(), MediaActionsListener {
|
|||||||
supportPostponeEnterTransition()
|
supportPostponeEnterTransition()
|
||||||
|
|
||||||
// Gather the parameters.
|
// Gather the parameters.
|
||||||
|
owningUsername = ViewMediaActivityIntent.getOwningUsername(intent)
|
||||||
attachmentViewData = ViewMediaActivityIntent.getAttachments(intent)
|
attachmentViewData = ViewMediaActivityIntent.getAttachments(intent)
|
||||||
val initialPosition = ViewMediaActivityIntent.getAttachmentIndex(intent)
|
val initialPosition = ViewMediaActivityIntent.getAttachmentIndex(intent)
|
||||||
|
|
||||||
@ -227,7 +229,11 @@ class ViewMediaActivity : BaseActivity(), MediaActionsListener {
|
|||||||
private fun downloadMedia() {
|
private fun downloadMedia() {
|
||||||
val url = imageUrl ?: attachmentViewData!![binding.viewPager.currentItem].attachment.url
|
val url = imageUrl ?: attachmentViewData!![binding.viewPager.currentItem].attachment.url
|
||||||
Toast.makeText(applicationContext, resources.getString(R.string.download_image, url), Toast.LENGTH_SHORT).show()
|
Toast.makeText(applicationContext, resources.getString(R.string.download_image, url), Toast.LENGTH_SHORT).show()
|
||||||
downloadUrlUseCase(url)
|
downloadUrlUseCase(
|
||||||
|
url,
|
||||||
|
accountManager.activeAccount!!.fullName,
|
||||||
|
owningUsername,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun requestDownloadMedia() {
|
private fun requestDownloadMedia() {
|
||||||
|
@ -889,7 +889,7 @@ abstract class StatusBaseViewHolder<T : IStatusViewData> protected constructor(i
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (card.kind == PreviewCardKind.PHOTO && card.embedUrl.isNotEmpty() && target == PreviewCardView.Target.IMAGE) {
|
if (card.kind == PreviewCardKind.PHOTO && card.embedUrl.isNotEmpty() && target == PreviewCardView.Target.IMAGE) {
|
||||||
context.startActivity(ViewMediaActivityIntent(context, card.embedUrl))
|
context.startActivity(ViewMediaActivityIntent(context, viewData.actionable.account.username, card.embedUrl))
|
||||||
return@bind
|
return@bind
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -568,18 +568,18 @@ class AccountActivity :
|
|||||||
.into(binding.accountHeaderImageView)
|
.into(binding.accountHeaderImageView)
|
||||||
|
|
||||||
binding.accountAvatarImageView.setOnClickListener { view ->
|
binding.accountAvatarImageView.setOnClickListener { view ->
|
||||||
viewImage(view, account.avatar)
|
viewImage(view, account.username, account.avatar)
|
||||||
}
|
}
|
||||||
binding.accountHeaderImageView.setOnClickListener { view ->
|
binding.accountHeaderImageView.setOnClickListener { view ->
|
||||||
viewImage(view, account.header)
|
viewImage(view, account.username, account.header)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun viewImage(view: View, uri: String) {
|
private fun viewImage(view: View, owningUsername: String, uri: String) {
|
||||||
ViewCompat.setTransitionName(view, uri)
|
ViewCompat.setTransitionName(view, uri)
|
||||||
startActivity(
|
startActivity(
|
||||||
ViewMediaActivityIntent(view.context, uri),
|
ViewMediaActivityIntent(view.context, owningUsername, uri),
|
||||||
ActivityOptionsCompat.makeSceneTransitionAnimation(this, view, uri).toBundle(),
|
ActivityOptionsCompat.makeSceneTransitionAnimation(this, view, uri).toBundle(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -165,7 +165,7 @@ class AccountMediaFragment :
|
|||||||
Attachment.Type.VIDEO,
|
Attachment.Type.VIDEO,
|
||||||
Attachment.Type.AUDIO,
|
Attachment.Type.AUDIO,
|
||||||
-> {
|
-> {
|
||||||
val intent = ViewMediaActivityIntent(requireContext(), attachmentsFromSameStatus, currentIndex)
|
val intent = ViewMediaActivityIntent(requireContext(), selected.username, attachmentsFromSameStatus, currentIndex)
|
||||||
if (activity != null) {
|
if (activity != null) {
|
||||||
val url = selected.attachment.url
|
val url = selected.attachment.url
|
||||||
ViewCompat.setTransitionName(view, url)
|
ViewCompat.setTransitionName(view, url)
|
||||||
|
@ -298,7 +298,12 @@ class ConversationsFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewMedia(viewData: ConversationViewData, attachmentIndex: Int, view: View?) {
|
override fun onViewMedia(viewData: ConversationViewData, attachmentIndex: Int, view: View?) {
|
||||||
viewMedia(attachmentIndex, AttachmentViewData.list(viewData.lastStatus.status), view)
|
viewMedia(
|
||||||
|
viewData.lastStatus.actionable.account.username,
|
||||||
|
attachmentIndex,
|
||||||
|
AttachmentViewData.list(viewData.lastStatus.status),
|
||||||
|
view,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewThread(status: Status) {
|
override fun onViewThread(status: Status) {
|
||||||
|
@ -546,6 +546,7 @@ class NotificationsFragment :
|
|||||||
|
|
||||||
override fun onViewMedia(viewData: NotificationViewData, attachmentIndex: Int, view: View?) {
|
override fun onViewMedia(viewData: NotificationViewData, attachmentIndex: Int, view: View?) {
|
||||||
super.viewMedia(
|
super.viewMedia(
|
||||||
|
viewData.statusViewData!!.status.account.username,
|
||||||
attachmentIndex,
|
attachmentIndex,
|
||||||
list(viewData.statusViewData!!.status, viewModel.statusDisplayOptions.value.showSensitiveMedia),
|
list(viewData.statusViewData!!.status, viewModel.statusDisplayOptions.value.showSensitiveMedia),
|
||||||
view,
|
view,
|
||||||
|
@ -82,7 +82,7 @@ class ReportStatusesFragment :
|
|||||||
when (actionable.attachments[idx].type) {
|
when (actionable.attachments[idx].type) {
|
||||||
Attachment.Type.GIFV, Attachment.Type.VIDEO, Attachment.Type.IMAGE, Attachment.Type.AUDIO -> {
|
Attachment.Type.GIFV, Attachment.Type.VIDEO, Attachment.Type.IMAGE, Attachment.Type.AUDIO -> {
|
||||||
val attachments = AttachmentViewData.list(actionable)
|
val attachments = AttachmentViewData.list(actionable)
|
||||||
val intent = ViewMediaActivityIntent(requireContext(), attachments, idx)
|
val intent = ViewMediaActivityIntent(requireContext(), actionable.account.username, attachments, idx)
|
||||||
if (v != null) {
|
if (v != null) {
|
||||||
val url = actionable.attachments[idx].url
|
val url = actionable.attachments[idx].url
|
||||||
ViewCompat.setTransitionName(v, url)
|
ViewCompat.setTransitionName(v, url)
|
||||||
|
@ -78,9 +78,6 @@ class SearchStatusesFragment : SearchFragment<StatusViewData>(), StatusActionLis
|
|||||||
override val data: Flow<PagingData<StatusViewData>>
|
override val data: Flow<PagingData<StatusViewData>>
|
||||||
get() = viewModel.statusesFlow
|
get() = viewModel.statusesFlow
|
||||||
|
|
||||||
private val searchAdapter
|
|
||||||
get() = super.adapter as SearchStatusesAdapter
|
|
||||||
|
|
||||||
override fun createAdapter(): PagingDataAdapter<StatusViewData, *> {
|
override fun createAdapter(): PagingDataAdapter<StatusViewData, *> {
|
||||||
val statusDisplayOptions = statusDisplayOptionsRepository.flow.value
|
val statusDisplayOptions = statusDisplayOptionsRepository.flow.value
|
||||||
|
|
||||||
@ -118,6 +115,7 @@ class SearchStatusesFragment : SearchFragment<StatusViewData>(), StatusActionLis
|
|||||||
val attachments = AttachmentViewData.list(actionable)
|
val attachments = AttachmentViewData.list(actionable)
|
||||||
val intent = ViewMediaActivityIntent(
|
val intent = ViewMediaActivityIntent(
|
||||||
requireContext(),
|
requireContext(),
|
||||||
|
actionable.account.username,
|
||||||
attachments,
|
attachments,
|
||||||
attachmentIndex,
|
attachmentIndex,
|
||||||
)
|
)
|
||||||
@ -381,7 +379,7 @@ class SearchStatusesFragment : SearchFragment<StatusViewData>(), StatusActionLis
|
|||||||
private fun downloadAllMedia(status: Status) {
|
private fun downloadAllMedia(status: Status) {
|
||||||
Toast.makeText(context, R.string.downloading_media, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, R.string.downloading_media, Toast.LENGTH_SHORT).show()
|
||||||
for ((_, url) in status.attachments) {
|
for ((_, url) in status.attachments) {
|
||||||
downloadUrlUseCase(url)
|
downloadUrlUseCase(url, viewModel.activeAccount!!.fullName, status.actionableStatus.account.username)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -707,7 +707,7 @@ class TimelineFragment :
|
|||||||
viewData.actionable
|
viewData.actionable
|
||||||
}
|
}
|
||||||
|
|
||||||
super.viewMedia(attachmentIndex, AttachmentViewData.list(actionable), view)
|
super.viewMedia(actionable.account.username, attachmentIndex, AttachmentViewData.list(actionable), view)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewThread(status: Status) {
|
override fun onViewThread(status: Status) {
|
||||||
|
@ -296,6 +296,7 @@ class ViewThreadFragment :
|
|||||||
|
|
||||||
override fun onViewMedia(viewData: StatusViewData, attachmentIndex: Int, view: View?) {
|
override fun onViewMedia(viewData: StatusViewData, attachmentIndex: Int, view: View?) {
|
||||||
super.viewMedia(
|
super.viewMedia(
|
||||||
|
viewData.username,
|
||||||
attachmentIndex,
|
attachmentIndex,
|
||||||
list(viewData.actionable, alwaysShowSensitiveMedia),
|
list(viewData.actionable, alwaysShowSensitiveMedia),
|
||||||
view,
|
view,
|
||||||
|
@ -399,11 +399,17 @@ abstract class SFragment<T : IStatusViewData> : Fragment(), StatusActionListener
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun viewMedia(urlIndex: Int, attachments: List<AttachmentViewData>, view: View?) {
|
/**
|
||||||
val (attachment) = attachments[urlIndex]
|
* @param owningUsername The username that "owns" this media. If this is media from a
|
||||||
|
* status then this is the username that posted the status. If this is media from an
|
||||||
|
* account (e.g., the account's avatar or header image) then this is the username of
|
||||||
|
* that account.
|
||||||
|
*/
|
||||||
|
protected fun viewMedia(owningUsername: String, urlIndex: Int, attachments: List<AttachmentViewData>, view: View?) {
|
||||||
|
val attachment = attachments[urlIndex].attachment
|
||||||
when (attachment.type) {
|
when (attachment.type) {
|
||||||
Attachment.Type.GIFV, Attachment.Type.VIDEO, Attachment.Type.IMAGE, Attachment.Type.AUDIO -> {
|
Attachment.Type.GIFV, Attachment.Type.VIDEO, Attachment.Type.IMAGE, Attachment.Type.AUDIO -> {
|
||||||
val intent = ViewMediaActivityIntent(requireContext(), attachments, urlIndex)
|
val intent = ViewMediaActivityIntent(requireContext(), owningUsername, attachments, urlIndex)
|
||||||
if (view != null) {
|
if (view != null) {
|
||||||
val url = attachment.url
|
val url = attachment.url
|
||||||
ViewCompat.setTransitionName(view, url)
|
ViewCompat.setTransitionName(view, url)
|
||||||
@ -545,7 +551,13 @@ abstract class SFragment<T : IStatusViewData> : Fragment(), StatusActionListener
|
|||||||
private fun downloadAllMedia(status: Status) {
|
private fun downloadAllMedia(status: Status) {
|
||||||
Toast.makeText(context, R.string.downloading_media, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, R.string.downloading_media, Toast.LENGTH_SHORT).show()
|
||||||
|
|
||||||
status.attachments.forEach { downloadUrlUseCase(it.url) }
|
status.attachments.forEach {
|
||||||
|
downloadUrlUseCase(
|
||||||
|
it.url,
|
||||||
|
accountManager.activeAccount!!.fullName,
|
||||||
|
status.actionableStatus.account.username,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun requestDownloadAllMedia(status: Status) {
|
private fun requestDownloadAllMedia(status: Status) {
|
||||||
|
@ -21,8 +21,9 @@ import android.app.DownloadManager
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import app.pachli.core.accounts.AccountManager
|
import app.pachli.core.preferences.DownloadLocation.DOWNLOADS
|
||||||
import app.pachli.core.preferences.DownloadLocation
|
import app.pachli.core.preferences.DownloadLocation.DOWNLOADS_PER_ACCOUNT
|
||||||
|
import app.pachli.core.preferences.DownloadLocation.DOWNLOADS_PER_SENDER
|
||||||
import app.pachli.core.preferences.SharedPreferencesRepository
|
import app.pachli.core.preferences.SharedPreferencesRepository
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -36,16 +37,21 @@ import javax.inject.Inject
|
|||||||
class DownloadUrlUseCase @Inject constructor(
|
class DownloadUrlUseCase @Inject constructor(
|
||||||
@ApplicationContext val context: Context,
|
@ApplicationContext val context: Context,
|
||||||
private val sharedPreferencesRepository: SharedPreferencesRepository,
|
private val sharedPreferencesRepository: SharedPreferencesRepository,
|
||||||
private val accountManager: AccountManager,
|
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Enqueues a [DownloadManager] request to download [url].
|
* Enqueues a [DownloadManager] request to download [url].
|
||||||
*
|
*
|
||||||
* The downloaded file is named after the URL's last path segment, and is
|
* The downloaded file is named after the URL's last path segment, and saved
|
||||||
* either saved to the "Downloads" directory, or a subdirectory named after
|
* according to the user's
|
||||||
* the user's account, depending on the app's preferences.
|
* [downloadLocation][SharedPreferencesRepository.downloadLocation] preference.
|
||||||
|
*
|
||||||
|
* @param url URL to download
|
||||||
|
* @param recipient Username of the account downloading the URL. Is expected
|
||||||
|
* to start with an "@"
|
||||||
|
* @param sender Username of the account supplying the URL. May or may not
|
||||||
|
* start with an "@", one is prepended to the download directory if missing.
|
||||||
*/
|
*/
|
||||||
operator fun invoke(url: String) {
|
operator fun invoke(url: String, recipient: String, sender: String) {
|
||||||
val uri = Uri.parse(url)
|
val uri = Uri.parse(url)
|
||||||
val filename = uri.lastPathSegment ?: return
|
val filename = uri.lastPathSegment ?: return
|
||||||
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||||
@ -54,11 +60,11 @@ class DownloadUrlUseCase @Inject constructor(
|
|||||||
val locationPref = sharedPreferencesRepository.downloadLocation
|
val locationPref = sharedPreferencesRepository.downloadLocation
|
||||||
|
|
||||||
val path = when (locationPref) {
|
val path = when (locationPref) {
|
||||||
DownloadLocation.DOWNLOADS -> filename
|
DOWNLOADS -> filename
|
||||||
DownloadLocation.DOWNLOADS_PER_ACCOUNT -> {
|
DOWNLOADS_PER_ACCOUNT -> File(recipient, filename).toString()
|
||||||
accountManager.activeAccount?.let {
|
DOWNLOADS_PER_SENDER -> {
|
||||||
File(it.fullName, filename).toString()
|
val finalSender = if (sender.startsWith("@")) sender else "@$sender"
|
||||||
} ?: filename
|
File(finalSender, filename).toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,8 @@ import kotlinx.parcelize.Parcelize
|
|||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class AttachmentViewData(
|
data class AttachmentViewData(
|
||||||
|
/** Username of the sender. With domain if remote, without domain if local. */
|
||||||
|
val username: String,
|
||||||
val attachment: Attachment,
|
val attachment: Attachment,
|
||||||
val statusId: String,
|
val statusId: String,
|
||||||
val statusUrl: String,
|
val statusUrl: String,
|
||||||
@ -41,6 +43,7 @@ data class AttachmentViewData(
|
|||||||
val actionable = status.actionableStatus
|
val actionable = status.actionableStatus
|
||||||
return actionable.attachments.map { attachment ->
|
return actionable.attachments.map { attachment ->
|
||||||
AttachmentViewData(
|
AttachmentViewData(
|
||||||
|
username = actionable.account.username,
|
||||||
attachment = attachment,
|
attachment = attachment,
|
||||||
statusId = actionable.id,
|
statusId = actionable.id,
|
||||||
statusUrl = actionable.url!!,
|
statusUrl = actionable.url!!,
|
||||||
|
@ -513,10 +513,13 @@ class ViewMediaActivityIntent private constructor(context: Context) : Intent() {
|
|||||||
* Show a collection of media attachments.
|
* Show a collection of media attachments.
|
||||||
*
|
*
|
||||||
* @param context
|
* @param context
|
||||||
|
* @param owningUsername The username that owns the media. See
|
||||||
|
* [SFragment.viewMedia][app.pachli.fragment.SFragment.viewMedia].
|
||||||
* @param attachments The attachments to show
|
* @param attachments The attachments to show
|
||||||
* @param index The index of the attachment in [attachments] to focus on
|
* @param index The index of the attachment in [attachments] to focus on
|
||||||
*/
|
*/
|
||||||
constructor(context: Context, attachments: List<AttachmentViewData>, index: Int) : this(context) {
|
constructor(context: Context, owningUsername: String, attachments: List<AttachmentViewData>, index: Int) : this(context) {
|
||||||
|
putExtra(EXTRA_OWNING_USERNAME, owningUsername)
|
||||||
putParcelableArrayListExtra(EXTRA_ATTACHMENTS, ArrayList(attachments))
|
putParcelableArrayListExtra(EXTRA_ATTACHMENTS, ArrayList(attachments))
|
||||||
putExtra(EXTRA_ATTACHMENT_INDEX, index)
|
putExtra(EXTRA_ATTACHMENT_INDEX, index)
|
||||||
}
|
}
|
||||||
@ -525,17 +528,24 @@ class ViewMediaActivityIntent private constructor(context: Context) : Intent() {
|
|||||||
* Show a single image identified by a URL
|
* Show a single image identified by a URL
|
||||||
*
|
*
|
||||||
* @param context
|
* @param context
|
||||||
|
* @param owningUsername The username that owns the media. See
|
||||||
|
* [SFragment.viewMedia][app.pachli.fragment.SFragment.viewMedia].
|
||||||
* @param url The URL of the image
|
* @param url The URL of the image
|
||||||
*/
|
*/
|
||||||
constructor(context: Context, url: String) : this(context) {
|
constructor(context: Context, owningUsername: String, url: String) : this(context) {
|
||||||
|
putExtra(EXTRA_OWNING_USERNAME, owningUsername)
|
||||||
putExtra(EXTRA_SINGLE_IMAGE_URL, url)
|
putExtra(EXTRA_SINGLE_IMAGE_URL, url)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private const val EXTRA_OWNING_USERNAME = "owningUsername"
|
||||||
private const val EXTRA_ATTACHMENTS = "attachments"
|
private const val EXTRA_ATTACHMENTS = "attachments"
|
||||||
private const val EXTRA_ATTACHMENT_INDEX = "index"
|
private const val EXTRA_ATTACHMENT_INDEX = "index"
|
||||||
private const val EXTRA_SINGLE_IMAGE_URL = "singleImage"
|
private const val EXTRA_SINGLE_IMAGE_URL = "singleImage"
|
||||||
|
|
||||||
|
/** @return the owningUsername passed in this intent. */
|
||||||
|
fun getOwningUsername(intent: Intent): String = intent.getStringExtra(EXTRA_OWNING_USERNAME)!!
|
||||||
|
|
||||||
/** @return the list of [AttachmentViewData] passed in this intent, or null */
|
/** @return the list of [AttachmentViewData] passed in this intent, or null */
|
||||||
fun getAttachments(intent: Intent): List<AttachmentViewData>? = IntentCompat.getParcelableArrayListExtra(intent, EXTRA_ATTACHMENTS, AttachmentViewData::class.java)
|
fun getAttachments(intent: Intent): List<AttachmentViewData>? = IntentCompat.getParcelableArrayListExtra(intent, EXTRA_ATTACHMENTS, AttachmentViewData::class.java)
|
||||||
|
|
||||||
|
@ -23,6 +23,9 @@ enum class DownloadLocation(override val displayResource: Int, override val valu
|
|||||||
/** Save to the root of the "Downloads" directory. */
|
/** Save to the root of the "Downloads" directory. */
|
||||||
DOWNLOADS(R.string.download_location_downloads),
|
DOWNLOADS(R.string.download_location_downloads),
|
||||||
|
|
||||||
/** Save in per-account folders in the "Downloads" directory. */
|
/** Save in per-account directories in the "Downloads" directory. */
|
||||||
DOWNLOADS_PER_ACCOUNT(R.string.download_location_per_account),
|
DOWNLOADS_PER_ACCOUNT(R.string.download_location_per_account),
|
||||||
|
|
||||||
|
/** Save in per-sender-account directories in the "Downloads" directory. */
|
||||||
|
DOWNLOADS_PER_SENDER(R.string.download_location_per_sender),
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
<string name="pref_title_downloads">Download location</string>
|
<string name="pref_title_downloads">Download location</string>
|
||||||
<string name="download_location_downloads">Downloads folder</string>
|
<string name="download_location_downloads">Downloads folder</string>
|
||||||
<string name="download_location_per_account">Per-account folders, in Downloads folder</string>
|
<string name="download_location_per_account">Per-account folders, in Downloads folder</string>
|
||||||
|
<string name="download_location_per_sender">Per-sender folders, in Downloads folder</string>
|
||||||
<string name="app_theme_light">Light</string>
|
<string name="app_theme_light">Light</string>
|
||||||
<string name="app_theme_black">Black</string>
|
<string name="app_theme_black">Black</string>
|
||||||
<string name="app_theme_auto">Automatic at sunset</string>
|
<string name="app_theme_auto">Automatic at sunset</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user