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:
OxygenCobalt 2021-03-26 09:54:48 -06:00
parent ac1e686704
commit b65814fdbd
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
11 changed files with 82 additions and 99 deletions

View file

@ -104,11 +104,6 @@ dependencies {
// Material // Material
implementation "com.google.android.material:material:1.3.0" 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 --- // --- DEBUG ---
// Lint // Lint

View file

@ -8,10 +8,6 @@
<queries /> <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 <application
android:name=".AuxioApp" android:name=".AuxioApp"
android:allowBackup="true" android:allowBackup="true"
@ -21,7 +17,6 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:exported="true" android:exported="true"
android:supportsRtl="true" android:supportsRtl="true"
android:requestLegacyExternalStorage="true"
android:theme="@style/Theme.Base"> android:theme="@style/Theme.Base">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"

View file

@ -1,14 +1,13 @@
package org.oxycblt.auxio.settings.accent package org.oxycblt.auxio.settings.accent
import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup 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.databinding.DialogAccentBinding
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.settings.ui.LifecycleDialog
import org.oxycblt.auxio.ui.ACCENTS import org.oxycblt.auxio.ui.ACCENTS
import org.oxycblt.auxio.ui.Accent import org.oxycblt.auxio.ui.Accent
import org.oxycblt.auxio.ui.inflater 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. * Dialog responsible for showing the list of accents to select.
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class AccentDialog : AppCompatDialogFragment() { class AccentDialog : LifecycleDialog() {
private val settingsManager = SettingsManager.getInstance() private val settingsManager = SettingsManager.getInstance()
private var pendingAccent = Accent.get() private var pendingAccent = Accent.get()
@ -59,6 +58,8 @@ class AccentDialog : AppCompatDialogFragment() {
updateAccent(binding) updateAccent(binding)
logD("Dialog created.")
return binding.root return binding.root
} }
@ -75,12 +76,6 @@ class AccentDialog : AppCompatDialogFragment() {
binding.accentConfirm.setTextColor(accentColor) binding.accentConfirm.setTextColor(accentColor)
} }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return MaterialAlertDialogBuilder(requireContext(), theme)
.setView(onCreateView(requireActivity().inflater, null, savedInstanceState))
.create()
}
companion object { companion object {
const val KEY_PENDING_ACCENT = "AXKEY_PEND_ACCENT" const val KEY_PENDING_ACCENT = "AXKEY_PEND_ACCENT"
} }

View file

@ -9,28 +9,24 @@ import kotlin.math.max
/** /**
* A sub-class of [GridLayoutManager] that automatically sets the spans so that they fit the width * 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( class AutoGridLayoutManager(
context: Context, context: Context,
attrs: AttributeSet, attrs: AttributeSet,
defStyleAttr: Int, defStyleAttr: Int,
defStyleRes: Int defStyleRes: Int
) : GridLayoutManager(context, attrs, defStyleAttr, defStyleRes) { ) : 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 lastWidth = -1
private var lastHeight = -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?) { override fun onLayoutChildren(recycler: RecyclerView.Recycler?, state: RecyclerView.State?) {
if (width > 0 && height > 0 && (lastWidth != width || lastHeight != height)) { if (width > 0 && height > 0 && (lastWidth != width || lastHeight != height)) {
val totalSpace = width - paddingRight - paddingLeft val totalSpace = width - paddingRight - paddingLeft

View file

@ -2,47 +2,39 @@ package org.oxycblt.auxio.settings.blacklist
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Environment import android.os.Environment
import android.provider.DocumentsContract
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.children import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels 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.MainActivity
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogBlacklistBinding import org.oxycblt.auxio.databinding.DialogBlacklistBinding
import org.oxycblt.auxio.logD import org.oxycblt.auxio.logD
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.settings.ui.LifecycleDialog
import org.oxycblt.auxio.ui.Accent import org.oxycblt.auxio.ui.Accent
import org.oxycblt.auxio.ui.createToast import org.oxycblt.auxio.ui.createToast
import org.oxycblt.auxio.ui.toColor import org.oxycblt.auxio.ui.toColor
import java.io.File
import kotlin.system.exitProcess import kotlin.system.exitProcess
/** /**
* Dialog that manages the currently excluded directories. * Dialog that manages the currently excluded directories.
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class BlacklistDialog : BottomSheetDialogFragment() { class BlacklistDialog : LifecycleDialog() {
private val blacklistModel: BlacklistViewModel by viewModels { private val blacklistModel: BlacklistViewModel by viewModels {
BlacklistViewModel.Factory(requireContext()) BlacklistViewModel.Factory(requireContext())
} }
private val playbackModel: PlaybackViewModel by activityViewModels() private val playbackModel: PlaybackViewModel by activityViewModels()
override fun getTheme() = R.style.Theme_BottomSheetFix
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -50,14 +42,16 @@ class BlacklistDialog : BottomSheetDialogFragment() {
): View { ): View {
val binding = DialogBlacklistBinding.inflate(inflater) val binding = DialogBlacklistBinding.inflate(inflater)
val launcher = registerForActivityResult(
ActivityResultContracts.OpenDocumentTree(), ::addDocTreePath
)
val accent = Accent.get().color.toColor(requireContext()) val accent = Accent.get().color.toColor(requireContext())
val adapter = BlacklistEntryAdapter { path -> val adapter = BlacklistEntryAdapter { path ->
blacklistModel.removePath(path) blacklistModel.removePath(path)
} }
requireContext().setTheme(Accent.get().theme)
// --- UI SETUP --- // --- UI SETUP ---
binding.blacklistRecycler.adapter = adapter binding.blacklistRecycler.adapter = adapter
@ -68,7 +62,8 @@ class BlacklistDialog : BottomSheetDialogFragment() {
setTextColor(accent) setTextColor(accent)
setOnClickListener { setOnClickListener {
showFileDialog() // showFileDialog()
launcher.launch(null)
} }
} }
@ -112,48 +107,33 @@ class BlacklistDialog : BottomSheetDialogFragment() {
blacklistModel.loadDatabasePaths() blacklistModel.loadDatabasePaths()
} }
private fun showFileDialog() { private fun addDocTreePath(uri: Uri) {
MaterialDialog(requireActivity()).show { val path = parseDocTreePath(uri)
positiveButton(R.string.label_add) {
onFolderSelected()
}
negativeButton() if (path != null) {
blacklistModel.addPath(path)
folderChooser( } else {
requireContext(), getString(R.string.error_bad_dir).createToast(requireContext())
initialDirectory = File(getRootPath()),
emptyTextRes = R.string.label_no_dirs
)
// Once again remove the ugly dividers from the dialog, but now with an even
// worse solution.
invalidateDividers(showTop = false, showBottom = false)
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)
}
})
} }
} }
private fun MaterialDialog.onFolderSelected() { private fun parseDocTreePath(uri: Uri): String? {
selectedFolder()?.absolutePath?.let { path -> // Turn the raw URI into a document tree URI
// Due to how Auxio's navigation flow works, dont allow the main root directory val docUri = DocumentsContract.buildDocumentUriUsingTree(
// to be excluded, as that would lead to the user being stuck at the "No Music Found" uri, DocumentsContract.getTreeDocumentId(uri)
// screen. )
if (path == getRootPath()) {
getString(R.string.error_brick_dir).createToast(requireContext())
return // Turn it into a semi-usable path
} val typeAndPath = DocumentsContract.getTreeDocumentId(docUri).split(":")
blacklistModel.addPath(path) // 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()
} }
return null
} }
private fun saveAndRestart() { private fun saveAndRestart() {
@ -163,7 +143,7 @@ class BlacklistDialog : BottomSheetDialogFragment() {
} }
private fun hardRestart() { private fun hardRestart() {
logD("Performing hard-restart.") logD("Performing hard restart.")
// Instead of having to do a ton of cleanup and horrible code changes // 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 // to restart this application non-destructively, I just restart the UI task [There is only
@ -176,6 +156,9 @@ class BlacklistDialog : BottomSheetDialogFragment() {
exitProcess(0) exitProcess(0)
} }
/**
* Get *just* the root path, nothing else is really needed.
*/
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
private fun getRootPath(): String { private fun getRootPath(): String {
return Environment.getExternalStorageDirectory().absolutePath return Environment.getExternalStorageDirectory().absolutePath

View file

@ -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)
}
}

View file

@ -7,7 +7,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/background" android:background="@color/background"
android:paddingBottom="@dimen/margin_medium" android:paddingBottom="@dimen/padding_small"
android:theme="@style/Theme.Neutral"> android:theme="@style/Theme.Neutral">
<TextView <TextView
@ -38,8 +38,8 @@
<Button <Button
android:id="@+id/accent_cancel" android:id="@+id/accent_cancel"
style="@style/Widget.Button.Dialog" style="@style/Widget.Button.Dialog"
android:layout_marginTop="@dimen/margin_medium" android:layout_marginTop="@dimen/margin_small"
android:layout_marginEnd="@dimen/padding_small" android:layout_marginEnd="@dimen/margin_small"
android:text="@android:string/cancel" android:text="@android:string/cancel"
app:layout_constraintEnd_toStartOf="@+id/accent_confirm" app:layout_constraintEnd_toStartOf="@+id/accent_confirm"
app:layout_constraintTop_toBottomOf="@+id/accent_recycler" /> app:layout_constraintTop_toBottomOf="@+id/accent_recycler" />
@ -47,7 +47,7 @@
<Button <Button
android:id="@+id/accent_confirm" android:id="@+id/accent_confirm"
style="@style/Widget.Button.Dialog" style="@style/Widget.Button.Dialog"
android:layout_marginEnd="@dimen/margin_medium" android:layout_marginEnd="@dimen/margin_small"
android:text="@android:string/ok" android:text="@android:string/ok"
app:layout_constraintBottom_toBottomOf="@+id/accent_cancel" app:layout_constraintBottom_toBottomOf="@+id/accent_cancel"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View file

@ -12,7 +12,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/background" android:background="@color/background"
android:orientation="vertical" android:orientation="vertical"
android:paddingBottom="@dimen/margin_medium" android:paddingBottom="@dimen/padding_small"
android:theme="@style/Theme.Neutral"> android:theme="@style/Theme.Neutral">
<TextView <TextView
@ -61,7 +61,7 @@
<Button <Button
android:id="@+id/blacklist_confirm" android:id="@+id/blacklist_confirm"
style="@style/Widget.Button.Dialog" style="@style/Widget.Button.Dialog"
android:layout_marginEnd="@dimen/margin_medium" android:layout_marginEnd="@dimen/margin_small"
android:text="@string/label_save" android:text="@string/label_save"
app:layout_constraintBottom_toBottomOf="@+id/blacklist_cancel" app:layout_constraintBottom_toBottomOf="@+id/blacklist_cancel"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -70,8 +70,8 @@
<Button <Button
android:id="@+id/blacklist_add" android:id="@+id/blacklist_add"
style="@style/Widget.Button.Dialog" style="@style/Widget.Button.Dialog"
android:layout_marginStart="@dimen/margin_medium" android:layout_marginStart="@dimen/margin_small"
android:layout_marginTop="@dimen/margin_medium" android:layout_marginTop="@dimen/margin_small"
android:text="@string/label_add" android:text="@string/label_add"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/blacklist_empty_text" /> app:layout_constraintTop_toBottomOf="@+id/blacklist_empty_text" />

View file

@ -106,6 +106,7 @@
<string name="error_no_perms">Auxio needs permission to read your music library</string> <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_no_browser">Could not open link</string>
<string name="error_brick_dir">The root folder cannot be excluded</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 --> <!-- Hint Namespace | EditText Hints -->
<string name="hint_search_library">Search your library…</string> <string name="hint_search_library">Search your library…</string>

View file

@ -20,12 +20,6 @@
<item name="colorControlActivated">?attr/colorPrimary</item> <item name="colorControlActivated">?attr/colorPrimary</item>
<item name="cornerRadius">0dp</item> <item name="cornerRadius">0dp</item>
<item name="colorSurface">@color/background</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> </style>
<!-- Toolbar theme --> <!-- Toolbar theme -->

View file

@ -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. ├──.coil # Fetchers and utilities for Coil, contains binding adapters than be used in the user interface.
├──.database # Databases and their items for Auxio ├──.database # Databases and their items for Auxio
├──.detail # UIs for more album/artist/genre details ├──.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 ├──.library # Library UI
├──.loading # Loading UI ├──.loading # Loading UI
├──.music # Music storage and loading ├──.music # Music storage and loading