Glimpse: Commonize file action dialogs and snackbars
Change-Id: I6af661a1fa062e3bd6dbf7a64a7d6354b272d8a8
diff --git a/app/src/main/java/org/lineageos/glimpse/ViewActivity.kt b/app/src/main/java/org/lineageos/glimpse/ViewActivity.kt
index 37c6992..9e75421 100644
--- a/app/src/main/java/org/lineageos/glimpse/ViewActivity.kt
+++ b/app/src/main/java/org/lineageos/glimpse/ViewActivity.kt
@@ -36,8 +36,6 @@
import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer
import androidx.viewpager2.widget.ViewPager2
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@@ -52,6 +50,7 @@
import org.lineageos.glimpse.models.UriMedia
import org.lineageos.glimpse.recyclerview.MediaViewerAdapter
import org.lineageos.glimpse.ui.MediaInfoBottomSheetDialog
+import org.lineageos.glimpse.utils.MediaDialogsUtils
import org.lineageos.glimpse.utils.MediaStoreBuckets
import org.lineageos.glimpse.utils.PermissionsGatedCallback
import org.lineageos.glimpse.viewmodels.MediaViewerUIViewModel
@@ -137,8 +136,7 @@
private var additionalMedias: Array<MediaStoreMedia>? = null
private var secure = false
- private var lastTrashedMedia: MediaStoreMedia? = null
- private var undoTrashSnackbar: Snackbar? = null
+ private var lastProcessedMedia: MediaStoreMedia? = null
/**
* Check if we're showing a static set of medias.
@@ -149,62 +147,46 @@
// Contracts
private val deleteUriContract =
registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
- Snackbar.make(
+ val succeeded = it.resultCode != Activity.RESULT_CANCELED
+
+ MediaDialogsUtils.showDeleteForeverResultSnackbar(
+ this,
bottomSheetLinearLayout,
- resources.getQuantityString(
- if (it.resultCode == Activity.RESULT_CANCELED) {
- R.plurals.delete_file_forever_unsuccessful
- } else {
- R.plurals.delete_file_forever_successful
- },
- 1, 1
- ),
- Snackbar.LENGTH_LONG,
- ).setAnchorView(bottomSheetLinearLayout).show()
+ succeeded, 1,
+ bottomSheetLinearLayout,
+ )
}
private val trashUriContract =
registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
val succeeded = it.resultCode != Activity.RESULT_CANCELED
- Snackbar.make(
+ MediaDialogsUtils.showMoveToTrashResultSnackbar(
+ this,
bottomSheetLinearLayout,
- resources.getQuantityString(
- if (succeeded) {
- R.plurals.move_file_to_trash_successful
- } else {
- R.plurals.move_file_to_trash_unsuccessful
- },
- 1, 1
- ),
- Snackbar.LENGTH_LONG,
- ).apply {
- anchorView = bottomSheetLinearLayout
- lastTrashedMedia?.takeIf { succeeded }?.let { trashedMedia ->
- setAction(R.string.move_file_to_trash_undo) {
- trashMedia(trashedMedia, false)
- }
- }
- undoTrashSnackbar = this
- }.show()
+ succeeded, 1,
+ bottomSheetLinearLayout,
+ lastProcessedMedia?.let { trashedMedia ->
+ { trashMedia(trashedMedia, false) }
+ },
+ )
- lastTrashedMedia = null
+ lastProcessedMedia = null
}
private val restoreUriFromTrashContract =
registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
- Snackbar.make(
+ val succeeded = it.resultCode != Activity.RESULT_CANCELED
+
+ MediaDialogsUtils.showRestoreFromTrashResultSnackbar(
+ this,
bottomSheetLinearLayout,
- resources.getQuantityString(
- if (it.resultCode == Activity.RESULT_CANCELED) {
- R.plurals.restore_file_from_trash_unsuccessful
- } else {
- R.plurals.restore_file_from_trash_successful
- },
- 1, 1
- ),
- Snackbar.LENGTH_LONG,
- ).setAnchorView(bottomSheetLinearLayout).show()
+ succeeded, 1,
+ bottomSheetLinearLayout,
+ lastProcessedMedia?.let { trashedMedia ->
+ { trashMedia(trashedMedia, true) }
+ },
+ )
}
private val favoriteContract =
@@ -399,23 +381,9 @@
deleteButton.setOnLongClickListener {
mediaViewerAdapter.getItemAtPosition(viewPager.currentItem).let {
- MaterialAlertDialogBuilder(this)
- .setTitle(R.string.file_action_delete_forever)
- .setMessage(
- resources.getQuantityString(
- R.plurals.delete_file_forever_confirm_message, 1, 1
- )
- ).setPositiveButton(android.R.string.ok) { _, _ ->
- deleteUriContract.launch(
- contentResolver.createDeleteRequest(
- it.uri
- )
- )
- }
- .setNegativeButton(android.R.string.cancel) { _, _ ->
- // Do nothing
- }
- .show()
+ MediaDialogsUtils.openDeleteForeverDialog(this, it.uri) { uris ->
+ deleteUriContract.launch(contentResolver.createDeleteRequest(*uris))
+ }
true
}
@@ -520,7 +488,7 @@
private fun trashMedia(media: MediaStoreMedia, trash: Boolean = !media.isTrashed) {
if (trash) {
- lastTrashedMedia = media
+ lastProcessedMedia = media
}
val contract = when (trash) {
diff --git a/app/src/main/java/org/lineageos/glimpse/fragments/AlbumViewerFragment.kt b/app/src/main/java/org/lineageos/glimpse/fragments/AlbumViewerFragment.kt
index 216e11b..69013a6 100644
--- a/app/src/main/java/org/lineageos/glimpse/fragments/AlbumViewerFragment.kt
+++ b/app/src/main/java/org/lineageos/glimpse/fragments/AlbumViewerFragment.kt
@@ -38,9 +38,7 @@
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.MaterialToolbar
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.shape.MaterialShapeDrawable
-import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.lineageos.glimpse.R
@@ -51,6 +49,7 @@
import org.lineageos.glimpse.recyclerview.ThumbnailAdapter
import org.lineageos.glimpse.recyclerview.ThumbnailItemDetailsLookup
import org.lineageos.glimpse.recyclerview.ThumbnailLayoutManager
+import org.lineageos.glimpse.utils.MediaDialogsUtils
import org.lineageos.glimpse.utils.MediaStoreBuckets
import org.lineageos.glimpse.utils.PermissionsGatedCallback
import org.lineageos.glimpse.viewmodels.AlbumViewerViewModel
@@ -154,47 +153,25 @@
selectionTracker?.selection?.toList()?.toTypedArray()?.takeUnless {
it.isEmpty()
}?.let { selection ->
- val count = selection.count()
-
when (item?.itemId) {
R.id.deleteForever -> {
- MaterialAlertDialogBuilder(requireContext())
- .setTitle(R.string.file_action_delete_forever)
- .setMessage(
- resources.getQuantityString(
- R.plurals.delete_file_forever_confirm_message, count, count
+ MediaDialogsUtils.openDeleteForeverDialog(requireContext(), *selection) {
+ deleteForeverContract.launch(
+ requireContext().contentResolver.createDeleteRequest(
+ *it.map { media ->
+ media.uri
+ }.toTypedArray()
)
- ).setPositiveButton(android.R.string.ok) { _, _ ->
- deleteForeverContract.launch(
- requireContext().contentResolver.createDeleteRequest(
- *selection.map { media ->
- media.uri
- }.toTypedArray()
- )
- )
- }
- .setNegativeButton(android.R.string.cancel) { _, _ ->
- // Do nothing
- }
- .show()
+ )
+ }
true
}
R.id.restoreFromTrash -> {
- MaterialAlertDialogBuilder(requireContext())
- .setTitle(R.string.file_action_restore_from_trash)
- .setMessage(
- resources.getQuantityString(
- R.plurals.restore_file_from_trash_confirm_message, count, count
- )
- ).setPositiveButton(android.R.string.ok) { _, _ ->
- trashMedias(false, *selection)
- }
- .setNegativeButton(android.R.string.cancel) { _, _ ->
- // Do nothing
- }
- .show()
+ MediaDialogsUtils.openRestoreFromTrashDialog(requireContext(), *selection) {
+ trashMedias(false, *selection)
+ }
true
}
@@ -206,19 +183,9 @@
}
R.id.moveToTrash -> {
- MaterialAlertDialogBuilder(requireContext())
- .setTitle(R.string.file_action_move_to_trash)
- .setMessage(
- resources.getQuantityString(
- R.plurals.move_file_to_trash_confirm_message, count, count
- )
- ).setPositiveButton(android.R.string.ok) { _, _ ->
- trashMedias(true, *selection)
- }
- .setNegativeButton(android.R.string.cancel) { _, _ ->
- // Do nothing
- }
- .show()
+ MediaDialogsUtils.openMoveToTrashDialog(requireContext(), *selection) {
+ trashMedias(true, *selection)
+ }
true
}
@@ -242,24 +209,17 @@
// Contracts
private var lastProcessedSelection: Array<out MediaStoreMedia>? = null
- private var undoTrashSnackbar: Snackbar? = null
private val deleteForeverContract =
registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
+ val succeeded = it.resultCode != Activity.RESULT_CANCELED
val count = lastProcessedSelection?.count() ?: 1
- Snackbar.make(
+ MediaDialogsUtils.showDeleteForeverResultSnackbar(
+ requireContext(),
requireView(),
- resources.getQuantityString(
- if (it.resultCode == Activity.RESULT_CANCELED) {
- R.plurals.delete_file_forever_unsuccessful
- } else {
- R.plurals.delete_file_forever_successful
- },
- count, count
- ),
- Snackbar.LENGTH_LONG,
- ).show()
+ succeeded, count,
+ )
lastProcessedSelection = null
selectionTracker?.clearSelection()
@@ -270,25 +230,16 @@
val succeeded = it.resultCode != Activity.RESULT_CANCELED
val count = lastProcessedSelection?.count() ?: 1
- Snackbar.make(
+ MediaDialogsUtils.showMoveToTrashResultSnackbar(
+ requireContext(),
requireView(),
- resources.getQuantityString(
- if (succeeded) {
- R.plurals.move_file_to_trash_successful
- } else {
- R.plurals.move_file_to_trash_unsuccessful
- },
- count, count
- ),
- Snackbar.LENGTH_LONG,
- ).apply {
- lastProcessedSelection?.takeIf { succeeded }?.let { trashedMedias ->
- setAction(R.string.move_file_to_trash_undo) {
+ succeeded, count,
+ actionCallback = lastProcessedSelection?.let { trashedMedias ->
+ {
trashMedias(false, *trashedMedias)
}
}
- undoTrashSnackbar = this
- }.show()
+ )
lastProcessedSelection = null
selectionTracker?.clearSelection()
@@ -296,20 +247,19 @@
private val restoreFromTrashContract =
registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
+ val succeeded = it.resultCode != Activity.RESULT_CANCELED
val count = lastProcessedSelection?.count() ?: 1
- Snackbar.make(
+ MediaDialogsUtils.showRestoreFromTrashResultSnackbar(
+ requireContext(),
requireView(),
- resources.getQuantityString(
- if (it.resultCode == Activity.RESULT_CANCELED) {
- R.plurals.restore_file_from_trash_unsuccessful
- } else {
- R.plurals.restore_file_from_trash_successful
- },
- count, count
- ),
- Snackbar.LENGTH_LONG,
- ).show()
+ succeeded, count,
+ actionCallback = lastProcessedSelection?.let { trashedMedias ->
+ {
+ trashMedias(true, *trashedMedias)
+ }
+ }
+ )
lastProcessedSelection = null
selectionTracker?.clearSelection()
@@ -346,29 +296,19 @@
R.id.emptyTrash -> {
val selection = thumbnailAdapter.currentList.mapNotNull {
AlbumViewerViewModel.DataType.Thumbnail::class.safeCast(it)?.media
- }
+ }.toTypedArray()
val count = selection.size
if (count > 0) {
- MaterialAlertDialogBuilder(requireContext())
- .setTitle(R.string.file_action_delete_forever)
- .setMessage(
- resources.getQuantityString(
- R.plurals.delete_file_forever_confirm_message, count, count
+ MediaDialogsUtils.openDeleteForeverDialog(requireContext(), *selection) {
+ deleteForeverContract.launch(
+ requireContext().contentResolver.createDeleteRequest(
+ *it.mapNotNull { media ->
+ MediaStoreMedia::class.safeCast(media)?.uri
+ }.toTypedArray()
)
- ).setPositiveButton(android.R.string.ok) { _, _ ->
- deleteForeverContract.launch(
- requireContext().contentResolver.createDeleteRequest(
- *selection.mapNotNull { media ->
- MediaStoreMedia::class.safeCast(media)?.uri
- }.toTypedArray()
- )
- )
- }
- .setNegativeButton(android.R.string.cancel) { _, _ ->
- // Do nothing
- }
- .show()
+ )
+ }
}
true
diff --git a/app/src/main/java/org/lineageos/glimpse/utils/MediaDialogsUtils.kt b/app/src/main/java/org/lineageos/glimpse/utils/MediaDialogsUtils.kt
new file mode 100644
index 0000000..a399c5d
--- /dev/null
+++ b/app/src/main/java/org/lineageos/glimpse/utils/MediaDialogsUtils.kt
@@ -0,0 +1,146 @@
+/*
+ * SPDX-FileCopyrightText: 2023 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.lineageos.glimpse.utils
+
+import android.content.Context
+import android.view.View
+import androidx.annotation.PluralsRes
+import androidx.annotation.StringRes
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.google.android.material.snackbar.Snackbar
+import org.lineageos.glimpse.R
+
+object MediaDialogsUtils {
+ private fun <T> openDialog(
+ context: Context,
+ vararg uris: T,
+ onPositiveCallback: (uris: Array<out T>) -> Unit,
+ @StringRes titleStringRes: Int,
+ @PluralsRes confirmMessagePluralsRes: Int,
+ ) {
+ val count = uris.size
+
+ MaterialAlertDialogBuilder(context)
+ .setTitle(titleStringRes)
+ .setMessage(
+ context.resources.getQuantityString(
+ confirmMessagePluralsRes, count, count
+ )
+ ).setPositiveButton(android.R.string.ok) { _, _ ->
+ onPositiveCallback(uris)
+ }
+ .setNegativeButton(android.R.string.cancel) { _, _ ->
+ // Do nothing
+ }
+ .show()
+ }
+
+ private fun showResultSnackbar(
+ context: Context,
+ view: View,
+ succeeded: Boolean,
+ count: Int,
+ anchorView: View? = null,
+ undoActionCallback: (() -> Unit)? = null,
+ @PluralsRes titleSuccessfulPluralsRes: Int,
+ @PluralsRes titleUnsuccessfulPluralsRes: Int,
+ ) = Snackbar.make(
+ view,
+ context.resources.getQuantityString(
+ if (succeeded) {
+ titleSuccessfulPluralsRes
+ } else {
+ titleUnsuccessfulPluralsRes
+ },
+ count, count
+ ),
+ Snackbar.LENGTH_LONG,
+ ).apply {
+ anchorView?.let {
+ this.anchorView = it
+ }
+
+ undoActionCallback?.takeIf { succeeded }?.let {
+ setAction(R.string.file_action_undo_action) { it() }
+ }
+
+ show()
+ }
+
+ // Move to trash
+
+ fun <T> openMoveToTrashDialog(
+ context: Context,
+ vararg uris: T,
+ onPositiveCallback: (uris: Array<out T>) -> Unit,
+ ) = openDialog(
+ context, *uris, onPositiveCallback = onPositiveCallback,
+ titleStringRes = R.string.file_action_move_to_trash,
+ confirmMessagePluralsRes = R.plurals.move_file_to_trash_confirm_message,
+ )
+
+ fun showMoveToTrashResultSnackbar(
+ context: Context,
+ view: View,
+ succeeded: Boolean,
+ count: Int,
+ anchorView: View? = null,
+ actionCallback: (() -> Unit)? = null,
+ ) = showResultSnackbar(
+ context, view, succeeded, count, anchorView, actionCallback,
+ titleSuccessfulPluralsRes = R.plurals.move_file_to_trash_successful,
+ titleUnsuccessfulPluralsRes = R.plurals.move_file_to_trash_unsuccessful,
+ )
+
+ // Restore from trash
+
+ fun <T> openRestoreFromTrashDialog(
+ context: Context,
+ vararg uris: T,
+ onPositiveCallback: (uris: Array<out T>) -> Unit,
+ ) = openDialog(
+ context, *uris, onPositiveCallback = onPositiveCallback,
+ titleStringRes = R.string.file_action_restore_from_trash,
+ confirmMessagePluralsRes = R.plurals.restore_file_from_trash_confirm_message,
+ )
+
+ fun showRestoreFromTrashResultSnackbar(
+ context: Context,
+ view: View,
+ succeeded: Boolean,
+ count: Int,
+ anchorView: View? = null,
+ actionCallback: (() -> Unit)? = null,
+ ) = showResultSnackbar(
+ context, view, succeeded, count, anchorView, actionCallback,
+ titleSuccessfulPluralsRes = R.plurals.restore_file_from_trash_successful,
+ titleUnsuccessfulPluralsRes = R.plurals.restore_file_from_trash_unsuccessful,
+ )
+
+ // Delete forever
+
+ fun <T> openDeleteForeverDialog(
+ context: Context,
+ vararg uris: T,
+ onPositiveCallback: (uris: Array<out T>) -> Unit,
+ ) = openDialog(
+ context, *uris, onPositiveCallback = onPositiveCallback,
+ titleStringRes = R.string.file_action_delete_forever,
+ confirmMessagePluralsRes = R.plurals.delete_file_forever_confirm_message,
+ )
+
+ fun showDeleteForeverResultSnackbar(
+ context: Context,
+ view: View,
+ succeeded: Boolean,
+ count: Int,
+ anchorView: View? = null,
+ ) = showResultSnackbar(
+ context, view, succeeded, count, anchorView,
+ titleSuccessfulPluralsRes = R.plurals.restore_file_from_trash_successful,
+ titleUnsuccessfulPluralsRes = R.plurals.restore_file_from_trash_unsuccessful,
+ )
+}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e8f99cc..6321293 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -52,7 +52,6 @@
<item quantity="one">File couldn\'t be moved to trash</item>
<item quantity="other">%d files couldn\'t be moved to trash</item>
</plurals>
- <string name="move_file_to_trash_undo">Undo</string>
<!-- File restoring from trash -->
<plurals name="restore_file_from_trash_confirm_message">
@@ -94,6 +93,9 @@
<string name="file_action_restore_from_trash">Restore from trash</string>
<string name="file_action_share">Share</string>
+ <!-- File actions - undo -->
+ <string name="file_action_undo_action">Undo</string>
+
<!-- No media -->
<string name="no_media">No media</string>