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
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

View file

@ -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"

View file

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

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
* 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

View file

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

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_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"

View file

@ -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" />

View file

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

View file

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

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.
├──.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