From aa0c978a6534a9c4c7fe73a9219ce3abfe92ce3a Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Sat, 13 Mar 2021 17:07:42 -0700 Subject: [PATCH] Add blacklist UI Add a UI for the blacklist functionality. --- app/build.gradle | 1 + app/src/main/AndroidManifest.xml | 1 + .../auxio/database/PlaybackStateDatabase.kt | 2 +- .../oxycblt/auxio/detail/DetailFragment.kt | 12 --- .../auxio/settings/SettingsFragment.kt | 9 +- .../auxio/settings/SettingsListFragment.kt | 10 ++- .../oxycblt/auxio/settings/SettingsManager.kt | 3 +- .../settings/blacklist/BlacklistDialog.kt | 85 +++++++++++++++++++ .../blacklist/BlacklistEntryAdapter.kt | 45 ++++++++++ .../settings/blacklist/BlacklistViewModel.kt | 26 ++++++ .../org/oxycblt/auxio/ui/InterfaceUtils.kt | 4 +- app/src/main/res/drawable/ic_add.xml | 11 +++ .../main/res/layout/fragment_blacklist.xml | 71 ++++++++++++++++ .../main/res/layout/item_blacklist_entry.xml | 33 +++++++ app/src/main/res/values-de/strings.xml | 6 +- app/src/main/res/values/dimens.xml | 4 + app/src/main/res/values/strings.xml | 17 +++- app/src/main/res/values/styles.xml | 11 +++ app/src/main/res/xml/prefs_main.xml | 16 +++- 19 files changed, 335 insertions(+), 32 deletions(-) create mode 100644 app/src/main/java/org/oxycblt/auxio/settings/blacklist/BlacklistDialog.kt create mode 100644 app/src/main/java/org/oxycblt/auxio/settings/blacklist/BlacklistEntryAdapter.kt create mode 100644 app/src/main/java/org/oxycblt/auxio/settings/blacklist/BlacklistViewModel.kt create mode 100644 app/src/main/res/drawable/ic_add.xml create mode 100644 app/src/main/res/layout/fragment_blacklist.xml create mode 100644 app/src/main/res/layout/item_blacklist_entry.xml diff --git a/app/build.gradle b/app/build.gradle index e63f5a932..bbef156ef 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -105,6 +105,7 @@ dependencies { // Dialogs implementation 'com.afollestad.material-dialogs:core:3.3.0' + implementation 'com.afollestad.material-dialogs:files:3.3.0' // --- DEV --- diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 06d462b82..051b31d24 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,6 +17,7 @@ android:roundIcon="@mipmap/ic_launcher_round" android:exported="true" android:supportsRtl="true" + android:requestLegacyExternalStorage="true" android:theme="@style/Theme.Base"> { + SettingsManager.Keys.KEY_SAVE_STATE -> { onPreferenceClickListener = Preference.OnPreferenceClickListener { playbackModel.savePlaybackState(requireContext()) true } } + + SettingsManager.Keys.KEY_BLACKLIST -> { + onPreferenceClickListener = Preference.OnPreferenceClickListener { + BlacklistDialog().show(childFragmentManager, TAG_ACCENT_DIALOG) + true + } + } } } } diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt index 5aa83289e..1c3dab7e3 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt @@ -231,7 +231,8 @@ class SettingsManager private constructor(context: Context) : const val KEY_LIBRARY_SORT_MODE = "KEY_LIBRARY_SORT_MODE" const val KEY_SEARCH_FILTER_MODE = "KEY_SEARCH" - const val KEY_DEBUG_SAVE = "KEY_SAVE_STATE" + const val KEY_SAVE_STATE = "KEY_SAVE_STATE" + const val KEY_BLACKLIST = "KEY_BLACKLIST" @Deprecated("Use the new KEY_ACCENT instead.") const val KEY_ACCENT_OLD = "KEY_ACCENT" diff --git a/app/src/main/java/org/oxycblt/auxio/settings/blacklist/BlacklistDialog.kt b/app/src/main/java/org/oxycblt/auxio/settings/blacklist/BlacklistDialog.kt new file mode 100644 index 000000000..e5dd98585 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/settings/blacklist/BlacklistDialog.kt @@ -0,0 +1,85 @@ +package org.oxycblt.auxio.settings.blacklist + +import android.os.Bundle +import android.os.Environment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.activityViewModels +import com.afollestad.materialdialogs.MaterialDialog +import com.afollestad.materialdialogs.WhichButton +import com.afollestad.materialdialogs.actions.setActionButtonEnabled +import com.afollestad.materialdialogs.files.folderChooser +import com.afollestad.materialdialogs.files.selectedFolder +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import org.oxycblt.auxio.R +import org.oxycblt.auxio.databinding.FragmentBlacklistBinding +import org.oxycblt.auxio.ui.createToast +import java.io.File + +class BlacklistDialog : BottomSheetDialogFragment() { + private val blacklistModel: BlacklistViewModel by activityViewModels() + + override fun getTheme() = R.style.Theme_BottomSheetFix + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val binding = FragmentBlacklistBinding.inflate(inflater) + + val adapter = BlacklistEntryAdapter { path -> + blacklistModel.removePath(path) + } + + // --- UI SETUP --- + + binding.blacklistRecycler.adapter = adapter + binding.blacklistAdd.setOnClickListener { + MaterialDialog(requireActivity()).show { + positiveButton(R.string.label_add) { + onFolderSelected() + } + + negativeButton() + + folderChooser( + requireContext(), + initialDirectory = File(Environment.getExternalStorageDirectory().absolutePath), + waitForPositiveButton = false, + emptyTextRes = R.string.error_no_dirs + ) + + // Still need to force-reenable the positive button even after flagging the + // disabling as false when setting up the file chooser + // Gotta love third-party libraries + setActionButtonEnabled(WhichButton.POSITIVE, true) + } + } + + // --- VIEWMODEL SETUP --- + + blacklistModel.paths.observe(viewLifecycleOwner) { paths -> + adapter.submitList(paths) + } + + return binding.root + } + + 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" + // error. + if (path == Environment.getExternalStorageDirectory().absolutePath) { + getString(R.string.error_folder_would_brick_app) + .createToast(requireContext()) + + return + } + + blacklistModel.addPath(path) + } + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/settings/blacklist/BlacklistEntryAdapter.kt b/app/src/main/java/org/oxycblt/auxio/settings/blacklist/BlacklistEntryAdapter.kt new file mode 100644 index 000000000..2ce4a50e6 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/settings/blacklist/BlacklistEntryAdapter.kt @@ -0,0 +1,45 @@ +package org.oxycblt.auxio.settings.blacklist + +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import org.oxycblt.auxio.databinding.ItemBlacklistEntryBinding +import org.oxycblt.auxio.ui.inflater + +class BlacklistEntryAdapter( + private val onClear: (String) -> Unit +) : RecyclerView.Adapter() { + private var paths = mutableListOf() + + override fun getItemCount() = paths.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + return ViewHolder(ItemBlacklistEntryBinding.inflate(parent.context.inflater)) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bind(paths[position]) + } + + fun submitList(newPaths: MutableList) { + paths = newPaths + notifyDataSetChanged() + } + + inner class ViewHolder( + private val binding: ItemBlacklistEntryBinding + ) : RecyclerView.ViewHolder(binding.root) { + init { + binding.root.layoutParams = RecyclerView.LayoutParams( + RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT + ) + } + + fun bind(path: String) { + binding.blacklistTitle.text = path + binding.blacklistTitle.requestLayout() + binding.blacklistClear.setOnClickListener { + onClear(path) + } + } + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/settings/blacklist/BlacklistViewModel.kt b/app/src/main/java/org/oxycblt/auxio/settings/blacklist/BlacklistViewModel.kt new file mode 100644 index 000000000..ba8ab7878 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/settings/blacklist/BlacklistViewModel.kt @@ -0,0 +1,26 @@ +package org.oxycblt.auxio.settings.blacklist + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +class BlacklistViewModel : ViewModel() { + private val mPaths = MutableLiveData(mutableListOf()) + val paths: LiveData> get() = mPaths + + fun addPath(path: String) { + if (mPaths.value!!.contains(path)) { + return + } + + mPaths.value!!.add(path) + + mPaths.value = mPaths.value + } + + fun removePath(path: String) { + mPaths.value!!.remove(path) + + mPaths.value = mPaths.value + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt b/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt index 368b3f8ed..94b7e4b1d 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt @@ -57,9 +57,9 @@ fun TextView.setTextColorResource(@ColorRes color: Int) { */ fun MaterialButton.applyAccents(highlighted: Boolean) { if (highlighted) { - backgroundTintList = Accent.get().color.toStateList(context) + backgroundTintList = Accent.get().getStateList(context) } else { - setTextColor(Accent.get().color.toColor(context)) + setTextColorResource(Accent.get().color) } } diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml new file mode 100644 index 000000000..64b5e7b70 --- /dev/null +++ b/app/src/main/res/drawable/ic_add.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_blacklist.xml b/app/src/main/res/layout/fragment_blacklist.xml new file mode 100644 index 000000000..ec4ec3c1c --- /dev/null +++ b/app/src/main/res/layout/fragment_blacklist.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + +