music: redesign music sources dialog

Now based around a more conventional design now that I no longer
need all the bells and whistles around include/exclude.
This commit is contained in:
Alexander Capehart 2024-12-26 18:22:28 -05:00
parent 75612dd1eb
commit 8d49893309
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
5 changed files with 116 additions and 81 deletions

View file

@ -27,6 +27,7 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.ConcatAdapter
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject import javax.inject.Inject
import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.BuildConfig
@ -45,8 +46,9 @@ import timber.log.Timber as L
*/ */
@AndroidEntryPoint @AndroidEntryPoint
class MusicSourcesDialog : class MusicSourcesDialog :
ViewBindingMaterialDialogFragment<DialogMusicLocationsBinding>(), LocationAdapter.Listener { ViewBindingMaterialDialogFragment<DialogMusicLocationsBinding>(), LocationAdapter.Listener, NewLocationFooterAdapter.Listener {
private val locationAdapter = LocationAdapter(this) private val locationAdapter = LocationAdapter(this)
private val locationFooterAdapter = NewLocationFooterAdapter(this)
private var openDocumentTreeLauncher: ActivityResultLauncher<Uri?>? = null private var openDocumentTreeLauncher: ActivityResultLauncher<Uri?>? = null
@Inject lateinit var musicSettings: MusicSettings @Inject lateinit var musicSettings: MusicSettings
@ -71,26 +73,8 @@ class MusicSourcesDialog :
registerForActivityResult( registerForActivityResult(
ActivityResultContracts.OpenDocumentTree(), ::addDocumentTreeUriToDirs) ActivityResultContracts.OpenDocumentTree(), ::addDocumentTreeUriToDirs)
binding.locationsAdd.apply {
ViewCompat.setTooltipText(this, contentDescription)
setOnClickListener {
L.d("Opening launcher")
val launcher =
requireNotNull(openDocumentTreeLauncher) {
"Document tree launcher was not available"
}
try {
launcher.launch(null)
} catch (e: ActivityNotFoundException) {
// User doesn't have a capable file manager.
requireContext().showToast(R.string.err_no_app)
}
}
}
binding.locationsRecycler.apply { binding.locationsRecycler.apply {
adapter = locationAdapter adapter = ConcatAdapter(locationAdapter, locationFooterAdapter)
itemAnimator = null itemAnimator = null
} }
@ -100,7 +84,6 @@ class MusicSourcesDialog :
} ?: musicSettings.musicLocations } ?: musicSettings.musicLocations
locationAdapter.addAll(locations) locationAdapter.addAll(locations)
requireBinding().locationsEmpty.isVisible = locations.isEmpty()
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
@ -117,7 +100,21 @@ class MusicSourcesDialog :
override fun onRemoveLocation(location: MusicLocation) { override fun onRemoveLocation(location: MusicLocation) {
locationAdapter.remove(location) locationAdapter.remove(location)
requireBinding().locationsEmpty.isVisible = locationAdapter.locations.isEmpty() }
override fun onNewLocation() {
L.d("Opening launcher")
val launcher =
requireNotNull(openDocumentTreeLauncher) {
"Document tree launcher was not available"
}
try {
launcher.launch(null)
} catch (e: ActivityNotFoundException) {
// User doesn't have a capable file manager.
requireContext().showToast(R.string.err_no_app)
}
} }
/** /**
@ -136,7 +133,6 @@ class MusicSourcesDialog :
if (location != null) { if (location != null) {
locationAdapter.add(location) locationAdapter.add(location)
requireBinding().locationsEmpty.isVisible = false
} else { } else {
requireContext().showToast(R.string.err_bad_location) requireContext().showToast(R.string.err_bad_location)
} }

View file

@ -0,0 +1,67 @@
package org.oxycblt.auxio.music.locations
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.ItemNewMusicLocationBinding
import org.oxycblt.auxio.databinding.ItemNewPlaylistChoiceBinding
import org.oxycblt.auxio.list.recycler.DialogRecyclerView
import org.oxycblt.auxio.music.decision.AddToPlaylistDialog
import org.oxycblt.auxio.util.inflater
/**
* A purely-visual [RecyclerView.Adapter] that acts as a footer providing a "New Playlist" choice in
* [AddToPlaylistDialog].
*
* @author Alexander Capehart (OxygenCobalt)
*/
class NewLocationFooterAdapter(private val listener: Listener) :
RecyclerView.Adapter<NewLocationFooterViewHolder>() {
override fun getItemCount() = 1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
NewLocationFooterViewHolder.from(parent)
override fun onBindViewHolder(holder: NewLocationFooterViewHolder, position: Int) {
holder.bind(listener)
}
/** A listener for [NewLocationFooterAdapter] interactions. */
interface Listener {
/**
* Called when the footer has been pressed, requesting to create a new location.
*/
fun onNewLocation()
}
}
/**
* A [RecyclerView.ViewHolder] that displays a "New Playlist" choice in [NewPlaylistFooterAdapter].
* Use [from] to create an instance.
*
* @author Alexander Capehart (OxygenCobalt)
*/
class NewLocationFooterViewHolder
private constructor(private val binding: ItemNewMusicLocationBinding) :
DialogRecyclerView.ViewHolder(binding.root) {
/**
* Bind new data to this instance.
*
* @param listener A [NewLocationFooterAdapter.Listener] to bind interactions to.
*/
fun bind(listener: NewLocationFooterAdapter.Listener) {
binding.root.setOnClickListener { listener.onNewLocation() }
}
companion object {
/**
* Create a new instance.
*
* @param parent The parent to inflate this instance from.
* @return A new instance.
*/
fun from(parent: View) =
NewLocationFooterViewHolder(
ItemNewMusicLocationBinding.inflate(parent.context.inflater))
}
}

View file

@ -1,65 +1,19 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
style="@style/Widget.Auxio.Dialog.NestedScrollView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout <org.oxycblt.auxio.list.recycler.DialogRecyclerView
android:id="@+id/locations_recycler"
style="@style/Widget.Auxio.RecyclerView.Linear"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:layout_gravity="center"
android:clipToPadding="false"
android:overScrollMode="never"
app:layout_constraintTop_toBottomOf="@+id/locations_list_header"
tools:listitem="@layout/item_music_location" />
<TextView </FrameLayout>
android:id="@+id/locations_list_header"
style="@style/Widget.Auxio.TextView.Header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/spacing_large"
android:paddingEnd="@dimen/spacing_large"
android:text="@string/set_locations_list"
app:layout_constraintTop_toTopOf="parent" />
<org.oxycblt.auxio.ui.RippleFixMaterialButton
android:id="@+id/locations_add"
style="@style/Widget.Auxio.Button.Icon.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/spacing_mid_large"
android:contentDescription="@string/lbl_add"
app:icon="@drawable/ic_add_24"
app:layout_constraintBottom_toTopOf="@+id/locations_recycler"
app:layout_constraintEnd_toEndOf="@+id/locations_list_header"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/locations_recycler"
style="@style/Widget.Auxio.RecyclerView.Linear"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_gravity="center"
android:layout_weight="1"
android:clipToPadding="false"
android:overScrollMode="never"
app:layout_constraintTop_toBottomOf="@+id/locations_list_header"
tools:listitem="@layout/item_music_location" />
<TextView
android:id="@+id/locations_empty"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/spacing_large"
android:paddingTop="@dimen/spacing_medium"
android:paddingEnd="@dimen/spacing_large"
android:paddingBottom="@dimen/spacing_medium"
android:text="@string/err_no_locations"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.Auxio.LabelLarge"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintTop_toTopOf="@+id/locations_recycler" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/new_location_label"
style="@style/Widget.Auxio.TextView.Icon.Clickable"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/set_locations_new"
android:paddingTop="@dimen/spacing_mid_large"
android:paddingBottom="@dimen/spacing_mid_large"
android:paddingStart="@dimen/spacing_mid_large"
android:paddingEnd="@dimen/spacing_large"
app:drawableStartCompat="@drawable/ic_add_24"
app:layout_constraintBottom_toTopOf="@+id/about_wiki"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/about_version" />

View file

@ -314,6 +314,7 @@
<string name="set_locations">Music folders</string> <string name="set_locations">Music folders</string>
<string name="set_locations_desc">Manage where music should be loaded from</string> <string name="set_locations_desc">Manage where music should be loaded from</string>
<string name="set_locations_list">Folders</string> <string name="set_locations_list">Folders</string>
<string name="set_locations_new">New folder</string>
<string name="set_reindex">Refresh music</string> <string name="set_reindex">Refresh music</string>
<string name="set_reindex_desc">Reload the music library, using cached tags when possible</string> <string name="set_reindex_desc">Reload the music library, using cached tags when possible</string>
<!-- Different from "Reload music" --> <!-- Different from "Reload music" -->