Add blacklist UI

Add a UI for the blacklist functionality.
This commit is contained in:
OxygenCobalt 2021-03-13 17:07:42 -07:00
parent 9d83619811
commit aa0c978a65
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
19 changed files with 335 additions and 32 deletions

View file

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

View file

@ -17,6 +17,7 @@
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

@ -163,7 +163,7 @@ class PlaybackStateDatabase(context: Context) :
var position = 0
// Try to write out the entirety of the queue. Failed inserts will be skipped.
// Try to write out the entirety of the queue.
while (position < queueItems.size) {
var i = position

View file

@ -93,18 +93,6 @@ abstract class DetailFragment : Fragment() {
}
}
}
// Since there is no elevation when the scroll position is zero, dont show
// the overscroll indicator.
addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
overScrollMode = if (computeVerticalScrollOffset() == 0) {
View.OVER_SCROLL_NEVER
} else {
View.OVER_SCROLL_IF_CONTENT_SCROLLS
}
}
})
}
}

View file

@ -5,7 +5,6 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentSettingsBinding
import org.oxycblt.auxio.settings.ui.AboutDialog
@ -22,12 +21,8 @@ class SettingsFragment : Fragment() {
val binding = FragmentSettingsBinding.inflate(inflater)
binding.settingsToolbar.setOnMenuItemClickListener {
if (it.itemId == R.id.action_open_about) {
AboutDialog().show(childFragmentManager, TAG_ABOUT_DIALOG)
true
} else {
false
}
}
return binding.root

View file

@ -13,6 +13,7 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.recycler.DisplayMode
import org.oxycblt.auxio.settings.blacklist.BlacklistDialog
import org.oxycblt.auxio.settings.ui.AccentDialog
import org.oxycblt.auxio.ui.Accent
@ -123,12 +124,19 @@ class SettingsListFragment : PreferenceFragmentCompat() {
}
}
SettingsManager.Keys.KEY_DEBUG_SAVE -> {
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
}
}
}
}
}

View file

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

View file

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

View file

@ -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<BlacklistEntryAdapter.ViewHolder>() {
private var paths = mutableListOf<String>()
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<String>) {
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)
}
}
}
}

View file

@ -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<String>())
val paths: LiveData<MutableList<String>> 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
}
}

View file

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

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="30dp"
android:height="30dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>

View file

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background"
android:theme="@style/Theme.Neutral"
android:paddingBottom="@dimen/margin_medium"
android:orientation="vertical">
<TextView
android:id="@+id/blacklist_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="@font/inter_exbold"
android:textColor="?attr/colorPrimary"
android:text="@string/setting_content_blacklist"
android:textSize="@dimen/text_size_toolbar_header"
android:padding="@dimen/padding_medium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/blacklist_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:overScrollMode="never"
tools:itemCount="2"
tools:listitem="@layout/item_blacklist_entry"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintTop_toBottomOf="@+id/blacklist_header" />
<TextView
android:id="@+id/blacklist_add"
style="@style/ItemText.Primary"
android:padding="@dimen/margin_medium"
android:layout_width="match_parent"
android:layout_marginStart="0dp"
android:text="@string/label_add"
android:gravity="center_vertical|start"
android:background="@drawable/ui_ripple"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
app:drawableStartCompat="@drawable/ic_add"
android:drawablePadding="@dimen/padding_medium"
app:layout_constraintTop_toBottomOf="@+id/blacklist_recycler" />
<Button
android:id="@+id/blacklist_cancel"
style="@style/Widget.Button.Dialog"
android:layout_marginEnd="@dimen/padding_small"
android:layout_marginTop="@dimen/margin_medium"
android:text="@android:string/cancel"
app:layout_constraintEnd_toStartOf="@+id/blacklist_confirm"
app:layout_constraintTop_toBottomOf="@+id/blacklist_add" />
<Button
android:id="@+id/blacklist_confirm"
style="@style/Widget.Button.Dialog"
android:layout_marginEnd="@dimen/margin_medium"
android:text="@string/label_confirm"
app:layout_constraintBottom_toBottomOf="@+id/blacklist_cancel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/blacklist_cancel" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.constraintlayout.widget.ConstraintLayout style="@style/ItemSurroundings"
android:clickable="false"
android:focusable="false">
<TextView
android:id="@+id/blacklist_title"
style="@style/ItemText.Primary"
android:layout_marginStart="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="/storage/emulated/0/directory" />
<ImageButton
android:id="@+id/blacklist_clear"
style="@style/Widget.Button.Unbounded"
android:layout_width="@dimen/size_clear"
android:layout_height="@dimen/size_clear"
android:src="@drawable/ic_clear"
android:contentDescription="@string/description_blacklist_delete"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -86,8 +86,8 @@
<string name="setting_behavior_keep_shuffle_desc">Lassen zufällig an, wenn ein neues Lied anspielen</string>
<string name="setting_behavior_rewind_prev">Zurückspulen, bevor zurück springen</string>
<string name="setting_behavior_rewind_prev_desc">Zurückspulen, bevor zum vorheriger Lied springen</string>
<string name="setting_behavior_save">Wiedergabezustand abspeichern</string>
<string name="setting_behavior_save_desc">Der aktuell Wiedergabezustand jetzt abspeichern</string>
<string name="setting_content_save">Wiedergabezustand abspeichern</string>
<string name="setting_content_save_desc">Der aktuell Wiedergabezustand jetzt abspeichern</string>
<!-- Error Namespace | Error Labels -->
<string name="error_no_music">Keine Musik gefunden</string>
@ -102,7 +102,7 @@
<string name="description_sort_button">Reihenfolge ändern</string>
<string name="description_track_number">Titel %d</string>
<string name="description_play_pause">Abspielen/Pausieren</string>
<string name="description_play_pause">Abspielen oder Pausieren</string>
<string name="description_skip_prev">Zu letzter Lied springen</string>
<string name="description_skip_next">Zu nächster Lied springen</string>
<string name="description_shuffle_on">Zufällig anschalten</string>

View file

@ -4,6 +4,7 @@
<dimen name="padding_microscopic">2dp</dimen>
<dimen name="padding_tiny">4dp</dimen>
<dimen name="padding_small">8dp</dimen>
<dimen name="padding_mid_small">12dp</dimen>
<dimen name="padding_medium">16dp</dimen>
<!-- Margin namespace | Dimens for margin attributes -->
@ -17,11 +18,13 @@
<!-- Height Namespace | Height for UI elements -->
<dimen name="height_compact_progress">2dp</dimen>
<dimen name="height_dialog_button">40dp</dimen>
<!-- Width Namespace | Width for UI elements -->
<dimen name="width_track_number">32dp</dimen>
<dimen name="width_thumb_view">50dp</dimen>
<dimen name="width_play_stroke">1dp</dimen>
<dimen name="width_dialog_button_min">64dp</dimen>
<!-- Size Namespace | Width & Heights for UI elements -->
<dimen name="size_error_icon">48dp</dimen>
@ -37,6 +40,7 @@
<dimen name="size_play_pause">70dp</dimen>
<dimen name="size_play_pause_compact">36dp</dimen>
<dimen name="size_clear">32dp</dimen>
<dimen name="size_app_icon">60dp</dimen>

View file

@ -50,6 +50,10 @@
<string name="label_licenses">Licenses</string>
<string name="label_author">Developed by OxygenCobalt</string>
<string name="label_add">Add</string>
<string name="label_confirm">Confirm</string>
<string name="label_empty_blacklist">No excluded folders</string>
<!-- Settings namespace | Settings-related labels -->
<string name="setting_title">Settings</string>
@ -89,14 +93,20 @@
<string name="setting_behavior_keep_shuffle_desc">Keep shuffle on when playing a new song</string>
<string name="setting_behavior_rewind_prev">Rewind before skipping back</string>
<string name="setting_behavior_rewind_prev_desc">Rewind before skipping to the previous song</string>
<string name="setting_behavior_save">Save playback state</string>
<string name="setting_behavior_save_desc">Save the current playback state now</string>
<string name="setting_content">Content</string>
<string name="setting_content_blacklist">Excluded Folders</string>
<string name="setting_content_blacklist_desc">The content of excluded folders is hidden from your library</string>
<string name="setting_content_save">Save playback state</string>
<string name="setting_content_save_desc">Save the current playback state now</string>
<!-- Error Namespace | Error Labels -->
<string name="error_no_music">No music found</string>
<string name="error_load_failed">Music loading failed</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_dirs">No subfolders</string>
<string name="error_folder_would_brick_app">The root folder can\'t be excluded</string>
<!-- Hint Namespace | EditText Hints -->
<string name="hint_search_library">Search your library…</string>
@ -105,7 +115,7 @@
<string name="description_sort_button">Change Sort Order</string>
<string name="description_track_number">Track %d</string>
<string name="description_play_pause">Play/Pause</string>
<string name="description_play_pause">Play or Pause</string>
<string name="description_skip_next">Skip to next song</string>
<string name="description_skip_prev">Skip to last song</string>
<string name="description_shuffle_on">Turn shuffle on</string>
@ -114,6 +124,7 @@
<string name="description_clear_user_queue">Clear queue</string>
<string name="description_clear_search">Clear search query</string>
<string name="description_blacklist_delete">Remove excluded directory</string>
<string name="description_error">Error</string>
<string name="description_auxio_icon">Auxio icon</string>

View file

@ -25,6 +25,7 @@
<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 -->
@ -239,4 +240,14 @@
<item name="android:scaleType">fitCenter</item>
<item name="android:padding">@dimen/padding_medium</item>
</style>
<!-- Style for dialog buttons -->
<style name="Widget.Button.Dialog" parent="Widget.AppCompat.Button">
<item name="android:layout_height">@dimen/height_dialog_button</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:paddingStart">@dimen/padding_mid_small</item>
<item name="android:paddingEnd">@dimen/padding_mid_small</item>
<item name="android:minWidth">@dimen/width_dialog_button_min</item>
<item name="android:background">@drawable/ui_ripple</item>
</style>
</resources>

View file

@ -126,10 +126,22 @@
app:key="KEY_PREV_REWIND"
app:summary="@string/setting_behavior_rewind_prev_desc" />
</PreferenceCategory>
<PreferenceCategory
android:layout="@layout/item_header"
android:title="@string/setting_content">
<Preference
android:title="@string/setting_behavior_save"
android:title="@string/setting_content_save"
app:iconSpaceReserved="false"
app:key="KEY_SAVE_STATE"
app:summary="@string/setting_behavior_save_desc" />
app:summary="@string/setting_content_save_desc" />
<Preference
android:title="@string/setting_content_blacklist"
app:iconSpaceReserved="false"
app:key="KEY_BLACKLIST"
app:summary="@string/setting_content_blacklist_desc" />
</PreferenceCategory>
</PreferenceScreen>