Redo blacklist dialog
Completely refactor the blacklist dialog to not only use the Storage Access Framework, but also to completely eliminate the material dialogs dependency.
This commit is contained in:
parent
ac1e686704
commit
b65814fdbd
11 changed files with 82 additions and 99 deletions
|
@ -104,11 +104,6 @@ dependencies {
|
|||
// Material
|
||||
implementation "com.google.android.material:material:1.3.0"
|
||||
|
||||
// Dialogs
|
||||
// TODO: Eliminate these, would eliminate ~100kb from the app size
|
||||
implementation "com.afollestad.material-dialogs:core:3.3.0"
|
||||
implementation "com.afollestad.material-dialogs:files:3.3.0"
|
||||
|
||||
// --- DEBUG ---
|
||||
|
||||
// Lint
|
||||
|
|
|
@ -8,10 +8,6 @@
|
|||
|
||||
<queries />
|
||||
|
||||
<!--
|
||||
TODO: Bite the bullet and just use SAF so you dont have to rely on a flag that is
|
||||
probably going to be removed by Android 12/13 (Would also eliminate the dialogs dependency)
|
||||
-->
|
||||
<application
|
||||
android:name=".AuxioApp"
|
||||
android:allowBackup="true"
|
||||
|
@ -21,7 +17,6 @@
|
|||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:exported="true"
|
||||
android:supportsRtl="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:theme="@style/Theme.Base">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
package org.oxycblt.auxio.settings.accent
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatDialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.oxycblt.auxio.databinding.DialogAccentBinding
|
||||
import org.oxycblt.auxio.logD
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.settings.ui.LifecycleDialog
|
||||
import org.oxycblt.auxio.ui.ACCENTS
|
||||
import org.oxycblt.auxio.ui.Accent
|
||||
import org.oxycblt.auxio.ui.inflater
|
||||
|
@ -18,7 +17,7 @@ import org.oxycblt.auxio.ui.toColor
|
|||
* Dialog responsible for showing the list of accents to select.
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
class AccentDialog : AppCompatDialogFragment() {
|
||||
class AccentDialog : LifecycleDialog() {
|
||||
private val settingsManager = SettingsManager.getInstance()
|
||||
private var pendingAccent = Accent.get()
|
||||
|
||||
|
@ -59,6 +58,8 @@ class AccentDialog : AppCompatDialogFragment() {
|
|||
|
||||
updateAccent(binding)
|
||||
|
||||
logD("Dialog created.")
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
|
@ -75,12 +76,6 @@ class AccentDialog : AppCompatDialogFragment() {
|
|||
binding.accentConfirm.setTextColor(accentColor)
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return MaterialAlertDialogBuilder(requireContext(), theme)
|
||||
.setView(onCreateView(requireActivity().inflater, null, savedInstanceState))
|
||||
.create()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KEY_PENDING_ACCENT = "AXKEY_PEND_ACCENT"
|
||||
}
|
||||
|
|
|
@ -9,28 +9,24 @@ import kotlin.math.max
|
|||
|
||||
/**
|
||||
* A sub-class of [GridLayoutManager] that automatically sets the spans so that they fit the width
|
||||
* of this dialog.
|
||||
* of the RecyclerView.
|
||||
* Adapted from this StackOverflow answer: https://stackoverflow.com/a/30256880/14143986
|
||||
*/
|
||||
@Suppress("UNUSED")
|
||||
class AutoGridLayoutManager(
|
||||
context: Context,
|
||||
attrs: AttributeSet,
|
||||
defStyleAttr: Int,
|
||||
defStyleRes: Int
|
||||
) : GridLayoutManager(context, attrs, defStyleAttr, defStyleRes) {
|
||||
private var columnWidth: Int = 0
|
||||
// We use 72dp here since that's the rough size of the accent item.
|
||||
// This will need to be modified if this is used beyond the accent dialog.
|
||||
private var columnWidth = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, 72F, context.resources.displayMetrics
|
||||
).toInt()
|
||||
|
||||
private var lastWidth = -1
|
||||
private var lastHeight = -1
|
||||
|
||||
init {
|
||||
// We use 72dp here since that's the rough size of the accent item, give or take.
|
||||
// This will need to be modified if this is used beyond the accent dialog.
|
||||
columnWidth = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, 72F,
|
||||
context.resources.displayMetrics
|
||||
).toInt()
|
||||
}
|
||||
|
||||
override fun onLayoutChildren(recycler: RecyclerView.Recycler?, state: RecyclerView.State?) {
|
||||
if (width > 0 && height > 0 && (lastWidth != width || lastHeight != height)) {
|
||||
val totalSpace = width - paddingRight - paddingLeft
|
||||
|
|
|
@ -2,47 +2,39 @@ package org.oxycblt.auxio.settings.blacklist
|
|||
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.provider.DocumentsContract
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.children
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.customview.getCustomView
|
||||
import com.afollestad.materialdialogs.files.folderChooser
|
||||
import com.afollestad.materialdialogs.files.selectedFolder
|
||||
import com.afollestad.materialdialogs.internal.list.DialogRecyclerView
|
||||
import com.afollestad.materialdialogs.utils.invalidateDividers
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import org.oxycblt.auxio.MainActivity
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.databinding.DialogBlacklistBinding
|
||||
import org.oxycblt.auxio.logD
|
||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||
import org.oxycblt.auxio.settings.ui.LifecycleDialog
|
||||
import org.oxycblt.auxio.ui.Accent
|
||||
import org.oxycblt.auxio.ui.createToast
|
||||
import org.oxycblt.auxio.ui.toColor
|
||||
import java.io.File
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
/**
|
||||
* Dialog that manages the currently excluded directories.
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
class BlacklistDialog : BottomSheetDialogFragment() {
|
||||
class BlacklistDialog : LifecycleDialog() {
|
||||
private val blacklistModel: BlacklistViewModel by viewModels {
|
||||
BlacklistViewModel.Factory(requireContext())
|
||||
}
|
||||
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
|
||||
override fun getTheme() = R.style.Theme_BottomSheetFix
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
@ -50,14 +42,16 @@ class BlacklistDialog : BottomSheetDialogFragment() {
|
|||
): View {
|
||||
val binding = DialogBlacklistBinding.inflate(inflater)
|
||||
|
||||
val launcher = registerForActivityResult(
|
||||
ActivityResultContracts.OpenDocumentTree(), ::addDocTreePath
|
||||
)
|
||||
|
||||
val accent = Accent.get().color.toColor(requireContext())
|
||||
|
||||
val adapter = BlacklistEntryAdapter { path ->
|
||||
blacklistModel.removePath(path)
|
||||
}
|
||||
|
||||
requireContext().setTheme(Accent.get().theme)
|
||||
|
||||
// --- UI SETUP ---
|
||||
|
||||
binding.blacklistRecycler.adapter = adapter
|
||||
|
@ -68,7 +62,8 @@ class BlacklistDialog : BottomSheetDialogFragment() {
|
|||
setTextColor(accent)
|
||||
|
||||
setOnClickListener {
|
||||
showFileDialog()
|
||||
// showFileDialog()
|
||||
launcher.launch(null)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,48 +107,33 @@ class BlacklistDialog : BottomSheetDialogFragment() {
|
|||
blacklistModel.loadDatabasePaths()
|
||||
}
|
||||
|
||||
private fun showFileDialog() {
|
||||
MaterialDialog(requireActivity()).show {
|
||||
positiveButton(R.string.label_add) {
|
||||
onFolderSelected()
|
||||
private fun addDocTreePath(uri: Uri) {
|
||||
val path = parseDocTreePath(uri)
|
||||
|
||||
if (path != null) {
|
||||
blacklistModel.addPath(path)
|
||||
} else {
|
||||
getString(R.string.error_bad_dir).createToast(requireContext())
|
||||
}
|
||||
}
|
||||
|
||||
negativeButton()
|
||||
|
||||
folderChooser(
|
||||
requireContext(),
|
||||
initialDirectory = File(getRootPath()),
|
||||
emptyTextRes = R.string.label_no_dirs
|
||||
private fun parseDocTreePath(uri: Uri): String? {
|
||||
// Turn the raw URI into a document tree URI
|
||||
val docUri = DocumentsContract.buildDocumentUriUsingTree(
|
||||
uri, DocumentsContract.getTreeDocumentId(uri)
|
||||
)
|
||||
|
||||
// Once again remove the ugly dividers from the dialog, but now with an even
|
||||
// worse solution.
|
||||
invalidateDividers(showTop = false, showBottom = false)
|
||||
// Turn it into a semi-usable path
|
||||
val typeAndPath = DocumentsContract.getTreeDocumentId(docUri).split(":")
|
||||
|
||||
val recycler = (getCustomView() as ViewGroup)
|
||||
.children.filterIsInstance<DialogRecyclerView>().firstOrNull()
|
||||
|
||||
recycler?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
invalidateDividers(showTop = false, showBottom = false)
|
||||
}
|
||||
})
|
||||
}
|
||||
// We only support the main drive since that's all we can get from MediaColumns.DATA.
|
||||
// We also check if this directory actually has multiple parts, if it isn't, then its
|
||||
// the root directory and it shouldn't be supported.
|
||||
if (typeAndPath[0] == "primary" && typeAndPath.size == 2) {
|
||||
return getRootPath() + "/" + typeAndPath.last()
|
||||
}
|
||||
|
||||
private fun MaterialDialog.onFolderSelected() {
|
||||
selectedFolder()?.absolutePath?.let { path ->
|
||||
// Due to how Auxio's navigation flow works, dont allow the main root directory
|
||||
// to be excluded, as that would lead to the user being stuck at the "No Music Found"
|
||||
// screen.
|
||||
if (path == getRootPath()) {
|
||||
getString(R.string.error_brick_dir).createToast(requireContext())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
blacklistModel.addPath(path)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun saveAndRestart() {
|
||||
|
@ -163,7 +143,7 @@ class BlacklistDialog : BottomSheetDialogFragment() {
|
|||
}
|
||||
|
||||
private fun hardRestart() {
|
||||
logD("Performing hard-restart.")
|
||||
logD("Performing hard restart.")
|
||||
|
||||
// Instead of having to do a ton of cleanup and horrible code changes
|
||||
// to restart this application non-destructively, I just restart the UI task [There is only
|
||||
|
@ -176,6 +156,9 @@ class BlacklistDialog : BottomSheetDialogFragment() {
|
|||
exitProcess(0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get *just* the root path, nothing else is really needed.
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
private fun getRootPath(): String {
|
||||
return Environment.getExternalStorageDirectory().absolutePath
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package org.oxycblt.auxio.settings.ui
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
||||
/**
|
||||
* [DialogFragment] that replicates the Fragment lifecycle in regards to [AlertDialog], which
|
||||
* doesn't seem to set the view from onCreateView correctly.
|
||||
*/
|
||||
abstract class LifecycleDialog() : DialogFragment() {
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return MaterialAlertDialogBuilder(requireActivity(), theme).create()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
(requireDialog() as AlertDialog).setView(view)
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/background"
|
||||
android:paddingBottom="@dimen/margin_medium"
|
||||
android:paddingBottom="@dimen/padding_small"
|
||||
android:theme="@style/Theme.Neutral">
|
||||
|
||||
<TextView
|
||||
|
@ -38,8 +38,8 @@
|
|||
<Button
|
||||
android:id="@+id/accent_cancel"
|
||||
style="@style/Widget.Button.Dialog"
|
||||
android:layout_marginTop="@dimen/margin_medium"
|
||||
android:layout_marginEnd="@dimen/padding_small"
|
||||
android:layout_marginTop="@dimen/margin_small"
|
||||
android:layout_marginEnd="@dimen/margin_small"
|
||||
android:text="@android:string/cancel"
|
||||
app:layout_constraintEnd_toStartOf="@+id/accent_confirm"
|
||||
app:layout_constraintTop_toBottomOf="@+id/accent_recycler" />
|
||||
|
@ -47,7 +47,7 @@
|
|||
<Button
|
||||
android:id="@+id/accent_confirm"
|
||||
style="@style/Widget.Button.Dialog"
|
||||
android:layout_marginEnd="@dimen/margin_medium"
|
||||
android:layout_marginEnd="@dimen/margin_small"
|
||||
android:text="@android:string/ok"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/accent_cancel"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
android:layout_height="match_parent"
|
||||
android:background="@color/background"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="@dimen/margin_medium"
|
||||
android:paddingBottom="@dimen/padding_small"
|
||||
android:theme="@style/Theme.Neutral">
|
||||
|
||||
<TextView
|
||||
|
@ -61,7 +61,7 @@
|
|||
<Button
|
||||
android:id="@+id/blacklist_confirm"
|
||||
style="@style/Widget.Button.Dialog"
|
||||
android:layout_marginEnd="@dimen/margin_medium"
|
||||
android:layout_marginEnd="@dimen/margin_small"
|
||||
android:text="@string/label_save"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/blacklist_cancel"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -70,8 +70,8 @@
|
|||
<Button
|
||||
android:id="@+id/blacklist_add"
|
||||
style="@style/Widget.Button.Dialog"
|
||||
android:layout_marginStart="@dimen/margin_medium"
|
||||
android:layout_marginTop="@dimen/margin_medium"
|
||||
android:layout_marginStart="@dimen/margin_small"
|
||||
android:layout_marginTop="@dimen/margin_small"
|
||||
android:text="@string/label_add"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/blacklist_empty_text" />
|
||||
|
|
|
@ -106,6 +106,7 @@
|
|||
<string name="error_no_perms">Auxio needs permission to read your music library</string>
|
||||
<string name="error_no_browser">Could not open link</string>
|
||||
<string name="error_brick_dir">The root folder cannot be excluded</string>
|
||||
<string name="error_bad_dir">That directory is not supported</string>
|
||||
|
||||
<!-- Hint Namespace | EditText Hints -->
|
||||
<string name="hint_search_library">Search your library…</string>
|
||||
|
|
|
@ -20,12 +20,6 @@
|
|||
<item name="colorControlActivated">?attr/colorPrimary</item>
|
||||
<item name="cornerRadius">0dp</item>
|
||||
<item name="colorSurface">@color/background</item>
|
||||
|
||||
<item name="md_background_color">@color/background</item>
|
||||
<item name="md_corner_radius">0dp</item>
|
||||
<item name="md_color_button_text">@color/control_color</item>
|
||||
<item name="md_font_title">@font/inter_exbold</item>
|
||||
<item name="md_ripple_color">@color/selection_color</item>
|
||||
</style>
|
||||
|
||||
<!-- Toolbar theme -->
|
||||
|
|
|
@ -38,7 +38,7 @@ org.oxycblt.auxio # Main UI's and logging utilities
|
|||
├──.coil # Fetchers and utilities for Coil, contains binding adapters than be used in the user interface.
|
||||
├──.database # Databases and their items for Auxio
|
||||
├──.detail # UIs for more album/artist/genre details
|
||||
│ └──.adapters # RecyclerView adapters for the detail UIs, which display the header information and items
|
||||
└──.adapters # RecyclerView adapters for the detail UIs, which display the header information and items
|
||||
├──.library # Library UI
|
||||
├──.loading # Loading UI
|
||||
├──.music # Music storage and loading
|
||||
|
|
Loading…
Reference in a new issue