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:
parent
75612dd1eb
commit
8d49893309
5 changed files with 116 additions and 81 deletions
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
|
17
app/src/main/res/layout/item_new_music_location.xml
Normal file
17
app/src/main/res/layout/item_new_music_location.xml
Normal 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" />
|
|
@ -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" -->
|
||||||
|
|
Loading…
Reference in a new issue