mirror of
https://gitlab.com/ultrasonic/ultrasonic.git
synced 2025-04-18 02:02:23 +03:00
Add nice looking empty list view
Also fix shouldRetry() in the Downloader
This commit is contained in:
parent
4e37a2483c
commit
b33fe2d451
@ -59,15 +59,10 @@ class ArtistListFragment : EntryListFragment<ArtistOrIndex>() {
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, item.name)
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_PARENT_ID, item.id)
|
||||
bundle.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, (item is Artist))
|
||||
bundle.putString(
|
||||
Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE,
|
||||
Constants.ALPHABETICAL_BY_NAME
|
||||
)
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, Constants.ALBUMS_OF_ARTIST)
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, item.name)
|
||||
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 1000)
|
||||
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0)
|
||||
findNavController().navigate(itemClickTarget, bundle)
|
||||
}
|
||||
|
||||
// Constants.ALPHABETICAL_BY_NAME
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import android.app.Application
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.LiveData
|
||||
import org.koin.core.component.inject
|
||||
@ -76,6 +77,9 @@ class DownloadsFragment : MultiListFragment<DownloadFile>() {
|
||||
|
||||
val liveDataList = listModel.getList()
|
||||
|
||||
emptyTextView.setText(R.string.download_empty)
|
||||
emptyView.isVisible = liveDataList.value?.isEmpty() ?: true
|
||||
|
||||
viewAdapter.submitList(liveDataList.value)
|
||||
}
|
||||
}
|
||||
|
@ -7,8 +7,11 @@ import androidx.navigation.fragment.findNavController
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.domain.Artist
|
||||
import org.moire.ultrasonic.domain.GenericEntry
|
||||
import org.moire.ultrasonic.domain.Identifiable
|
||||
import org.moire.ultrasonic.service.RxBus
|
||||
import org.moire.ultrasonic.subsonic.DownloadHandler
|
||||
import org.moire.ultrasonic.util.Constants
|
||||
import androidx.fragment.app.Fragment
|
||||
import org.moire.ultrasonic.util.Settings
|
||||
|
||||
/**
|
||||
@ -27,91 +30,11 @@ abstract class EntryListFragment<T : GenericEntry> : MultiListFragment<T>() {
|
||||
!listModel.isOffline() && !Settings.shouldUseId3Tags
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
|
||||
override fun onContextMenuItemSelected(menuItem: MenuItem, item: T): Boolean {
|
||||
val isArtist = (item is Artist)
|
||||
|
||||
when (menuItem.itemId) {
|
||||
R.id.menu_play_now ->
|
||||
downloadHandler.downloadRecursively(
|
||||
this,
|
||||
item.id,
|
||||
save = false,
|
||||
append = false,
|
||||
autoPlay = true,
|
||||
shuffle = false,
|
||||
background = false,
|
||||
playNext = false,
|
||||
unpin = false,
|
||||
isArtist = isArtist
|
||||
)
|
||||
R.id.menu_play_next ->
|
||||
downloadHandler.downloadRecursively(
|
||||
this,
|
||||
item.id,
|
||||
save = false,
|
||||
append = false,
|
||||
autoPlay = true,
|
||||
shuffle = true,
|
||||
background = false,
|
||||
playNext = true,
|
||||
unpin = false,
|
||||
isArtist = isArtist
|
||||
)
|
||||
R.id.menu_play_last ->
|
||||
downloadHandler.downloadRecursively(
|
||||
this,
|
||||
item.id,
|
||||
save = false,
|
||||
append = true,
|
||||
autoPlay = false,
|
||||
shuffle = false,
|
||||
background = false,
|
||||
playNext = false,
|
||||
unpin = false,
|
||||
isArtist = isArtist
|
||||
)
|
||||
R.id.menu_pin ->
|
||||
downloadHandler.downloadRecursively(
|
||||
this,
|
||||
item.id,
|
||||
save = true,
|
||||
append = true,
|
||||
autoPlay = false,
|
||||
shuffle = false,
|
||||
background = false,
|
||||
playNext = false,
|
||||
unpin = false,
|
||||
isArtist = isArtist
|
||||
)
|
||||
R.id.menu_unpin ->
|
||||
downloadHandler.downloadRecursively(
|
||||
this,
|
||||
item.id,
|
||||
save = false,
|
||||
append = false,
|
||||
autoPlay = false,
|
||||
shuffle = false,
|
||||
background = false,
|
||||
playNext = false,
|
||||
unpin = true,
|
||||
isArtist = isArtist
|
||||
)
|
||||
R.id.menu_download ->
|
||||
downloadHandler.downloadRecursively(
|
||||
this,
|
||||
item.id,
|
||||
save = false,
|
||||
append = false,
|
||||
autoPlay = false,
|
||||
shuffle = false,
|
||||
background = true,
|
||||
playNext = false,
|
||||
unpin = false,
|
||||
isArtist = isArtist
|
||||
)
|
||||
}
|
||||
return true
|
||||
return handleContextMenu(menuItem, item, isArtist, downloadHandler, this)
|
||||
}
|
||||
|
||||
override fun onItemClick(item: T) {
|
||||
@ -137,4 +60,97 @@ abstract class EntryListFragment<T : GenericEntry> : MultiListFragment<T>() {
|
||||
listModel.refresh(refreshListView!!, arguments)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@Suppress("LongMethod")
|
||||
internal fun handleContextMenu(
|
||||
menuItem: MenuItem,
|
||||
item: Identifiable,
|
||||
isArtist: Boolean,
|
||||
downloadHandler: DownloadHandler,
|
||||
fragment: Fragment
|
||||
): Boolean {
|
||||
when (menuItem.itemId) {
|
||||
R.id.menu_play_now ->
|
||||
downloadHandler.downloadRecursively(
|
||||
fragment,
|
||||
item.id,
|
||||
save = false,
|
||||
append = false,
|
||||
autoPlay = true,
|
||||
shuffle = false,
|
||||
background = false,
|
||||
playNext = false,
|
||||
unpin = false,
|
||||
isArtist = isArtist
|
||||
)
|
||||
R.id.menu_play_next ->
|
||||
downloadHandler.downloadRecursively(
|
||||
fragment,
|
||||
item.id,
|
||||
save = false,
|
||||
append = false,
|
||||
autoPlay = true,
|
||||
shuffle = true,
|
||||
background = false,
|
||||
playNext = true,
|
||||
unpin = false,
|
||||
isArtist = isArtist
|
||||
)
|
||||
R.id.menu_play_last ->
|
||||
downloadHandler.downloadRecursively(
|
||||
fragment,
|
||||
item.id,
|
||||
save = false,
|
||||
append = true,
|
||||
autoPlay = false,
|
||||
shuffle = false,
|
||||
background = false,
|
||||
playNext = false,
|
||||
unpin = false,
|
||||
isArtist = isArtist
|
||||
)
|
||||
R.id.menu_pin ->
|
||||
downloadHandler.downloadRecursively(
|
||||
fragment,
|
||||
item.id,
|
||||
save = true,
|
||||
append = true,
|
||||
autoPlay = false,
|
||||
shuffle = false,
|
||||
background = false,
|
||||
playNext = false,
|
||||
unpin = false,
|
||||
isArtist = isArtist
|
||||
)
|
||||
R.id.menu_unpin ->
|
||||
downloadHandler.downloadRecursively(
|
||||
fragment,
|
||||
item.id,
|
||||
save = false,
|
||||
append = false,
|
||||
autoPlay = false,
|
||||
shuffle = false,
|
||||
background = false,
|
||||
playNext = false,
|
||||
unpin = true,
|
||||
isArtist = isArtist
|
||||
)
|
||||
R.id.menu_download ->
|
||||
downloadHandler.downloadRecursively(
|
||||
fragment,
|
||||
item.id,
|
||||
save = false,
|
||||
append = false,
|
||||
autoPlay = false,
|
||||
shuffle = false,
|
||||
background = true,
|
||||
playNext = false,
|
||||
unpin = false,
|
||||
isArtist = isArtist
|
||||
)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
@ -46,6 +47,7 @@ abstract class MultiListFragment<T : Identifiable> : Fragment() {
|
||||
protected var refreshListView: SwipeRefreshLayout? = null
|
||||
internal var listView: RecyclerView? = null
|
||||
internal lateinit var viewManager: LinearLayoutManager
|
||||
internal lateinit var emptyView: ConstraintLayout
|
||||
internal lateinit var emptyTextView: TextView
|
||||
|
||||
/**
|
||||
@ -71,7 +73,7 @@ abstract class MultiListFragment<T : Identifiable> : Fragment() {
|
||||
* The central function to pass a query to the model and return a LiveData object
|
||||
*/
|
||||
open fun getLiveData(args: Bundle? = null): LiveData<List<T>> {
|
||||
return MutableLiveData(listOf())
|
||||
return MutableLiveData()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -90,7 +92,9 @@ abstract class MultiListFragment<T : Identifiable> : Fragment() {
|
||||
*/
|
||||
open val refreshListId = R.id.swipe_refresh_view
|
||||
open val recyclerViewId = R.id.recycler_view
|
||||
open val emptyTextViewId = R.id.empty_list_text
|
||||
open val emptyViewId = R.id.empty_list_view
|
||||
open val emptyTextId = R.id.empty_list_text
|
||||
|
||||
|
||||
open fun setTitle(title: String?) {
|
||||
if (title == null) {
|
||||
@ -121,14 +125,14 @@ abstract class MultiListFragment<T : Identifiable> : Fragment() {
|
||||
liveDataItems = getLiveData(arguments)
|
||||
|
||||
// Link view to display text if the list is empty
|
||||
// FIXME: Hook this up globally.
|
||||
emptyTextView = view.findViewById(emptyTextViewId)
|
||||
emptyView = view.findViewById(emptyViewId)
|
||||
emptyTextView = view.findViewById(emptyTextId)
|
||||
|
||||
// Register an observer to update our UI when the data changes
|
||||
liveDataItems.observe(
|
||||
viewLifecycleOwner,
|
||||
{ newItems ->
|
||||
emptyTextView.isVisible = newItems.isEmpty()
|
||||
emptyView.isVisible = newItems.isEmpty()
|
||||
viewAdapter.submitList(newItems)
|
||||
}
|
||||
)
|
||||
|
@ -28,6 +28,7 @@ import org.moire.ultrasonic.adapters.AlbumRowBinder
|
||||
import org.moire.ultrasonic.adapters.ArtistRowBinder
|
||||
import org.moire.ultrasonic.adapters.DividerBinder
|
||||
import org.moire.ultrasonic.adapters.TrackViewBinder
|
||||
import org.moire.ultrasonic.domain.Artist
|
||||
import org.moire.ultrasonic.domain.Identifiable
|
||||
import org.moire.ultrasonic.domain.MusicDirectory
|
||||
import org.moire.ultrasonic.domain.SearchResult
|
||||
@ -176,11 +177,11 @@ class SearchFragment : MultiListFragment<Identifiable>(), KoinComponent {
|
||||
return search(query, autoPlay)
|
||||
}
|
||||
}
|
||||
|
||||
// Fragment was started from the Menu, create empty list
|
||||
// populateList(SearchResult())
|
||||
}
|
||||
|
||||
/**
|
||||
* This method create the search bar above the recycler view
|
||||
*/
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
val activity = activity ?: return
|
||||
val searchManager = activity.getSystemService(Context.SEARCH_SERVICE) as SearchManager
|
||||
@ -191,8 +192,8 @@ class SearchFragment : MultiListFragment<Identifiable>(), KoinComponent {
|
||||
searchView.setSearchableInfo(searchableInfo)
|
||||
|
||||
val arguments = arguments
|
||||
val autoPlay =
|
||||
arguments != null && arguments.getBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false)
|
||||
val autoPlay = arguments != null &&
|
||||
arguments.getBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false)
|
||||
val query = arguments?.getString(Constants.INTENT_EXTRA_NAME_QUERY)
|
||||
|
||||
// If started with a query, enter it to the searchView
|
||||
@ -211,13 +212,13 @@ class SearchFragment : MultiListFragment<Identifiable>(), KoinComponent {
|
||||
val cursor = searchView.suggestionsAdapter.cursor
|
||||
cursor.moveToPosition(position)
|
||||
|
||||
// TODO: Try to do something with this magic const:
|
||||
// 2 is the index of col containing suggestion name.
|
||||
// 2 is the index of col containing suggestion name.
|
||||
val suggestion = cursor.getString(2)
|
||||
searchView.setQuery(suggestion, true)
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||
override fun onQueryTextSubmit(query: String): Boolean {
|
||||
Timber.d("onQueryTextSubmit: %s", query)
|
||||
@ -230,6 +231,7 @@ class SearchFragment : MultiListFragment<Identifiable>(), KoinComponent {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
searchView.setIconifiedByDefault(false)
|
||||
searchItem.expandActionView()
|
||||
}
|
||||
@ -479,7 +481,7 @@ class SearchFragment : MultiListFragment<Identifiable>(), KoinComponent {
|
||||
}
|
||||
|
||||
// Show/hide the empty text view
|
||||
emptyTextView.isVisible = list.isEmpty()
|
||||
emptyView.isVisible = list.isEmpty()
|
||||
|
||||
viewAdapter.submitList(list)
|
||||
}
|
||||
@ -557,20 +559,15 @@ class SearchFragment : MultiListFragment<Identifiable>(), KoinComponent {
|
||||
var DEFAULT_SONGS = Settings.defaultSongs
|
||||
}
|
||||
|
||||
// FIXME!!
|
||||
override fun getLiveData(args: Bundle?): LiveData<List<Identifiable>> {
|
||||
return MutableLiveData(listOf())
|
||||
}
|
||||
|
||||
// FIXME
|
||||
override val itemClickTarget: Int = 0
|
||||
|
||||
// FIXME
|
||||
override fun onContextMenuItemSelected(menuItem: MenuItem, item: Identifiable): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
// FIXME
|
||||
override fun onItemClick(item: Identifiable) {
|
||||
}
|
||||
|
||||
override fun onContextMenuItemSelected(menuItem: MenuItem, item: Identifiable): Boolean {
|
||||
val isArtist = (item is Artist)
|
||||
return EntryListFragment.handleContextMenu(menuItem, item, isArtist, downloadHandler, this)
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,6 @@ import timber.log.Timber
|
||||
open class TrackCollectionFragment : MultiListFragment<MusicDirectory.Entry>() {
|
||||
|
||||
private var albumButtons: View? = null
|
||||
private var emptyView: TextView? = null
|
||||
internal var selectButton: ImageView? = null
|
||||
internal var playNowButton: ImageView? = null
|
||||
internal var playNextButton: ImageView? = null
|
||||
@ -107,8 +106,6 @@ open class TrackCollectionFragment : MultiListFragment<MusicDirectory.Entry>() {
|
||||
|
||||
setupButtons(view)
|
||||
|
||||
emptyView = view.findViewById(R.id.empty_list_text)
|
||||
|
||||
registerForContextMenu(listView!!)
|
||||
setHasOptionsMenu(true)
|
||||
|
||||
@ -579,7 +576,7 @@ open class TrackCollectionFragment : MultiListFragment<MusicDirectory.Entry>() {
|
||||
}
|
||||
|
||||
// Show a text if we have no entries
|
||||
emptyView?.isVisible = entryList.isEmpty()
|
||||
emptyView.isVisible = entryList.isEmpty()
|
||||
|
||||
enableButtons()
|
||||
|
||||
@ -599,10 +596,8 @@ open class TrackCollectionFragment : MultiListFragment<MusicDirectory.Entry>() {
|
||||
val albumHeader = AlbumHeader(it, name ?: intentAlbumName)
|
||||
val mixedList: MutableList<Identifiable> = mutableListOf(albumHeader)
|
||||
mixedList.addAll(entryList)
|
||||
Timber.e("SUBMITTING MIXED LIST")
|
||||
viewAdapter.submitList(mixedList)
|
||||
} else {
|
||||
Timber.e("SUBMITTING ENTRY LIST")
|
||||
viewAdapter.submitList(entryList)
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ import org.moire.ultrasonic.util.Settings
|
||||
|
||||
class AlbumListModel(application: Application) : GenericListModel(application) {
|
||||
|
||||
val list: MutableLiveData<List<MusicDirectory.Album>> = MutableLiveData(listOf())
|
||||
val list: MutableLiveData<List<MusicDirectory.Album>> = MutableLiveData()
|
||||
var lastType: String? = null
|
||||
private var loadedUntil: Int = 0
|
||||
|
||||
@ -26,7 +26,7 @@ class AlbumListModel(application: Application) : GenericListModel(application) {
|
||||
// This way, we keep the scroll position
|
||||
val albumListType = args.getString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE)!!
|
||||
|
||||
if (refresh || list.value!!.isEmpty() || albumListType != lastType) {
|
||||
if (refresh || list.value?.isEmpty() != false || albumListType != lastType) {
|
||||
lastType = albumListType
|
||||
backgroundLoadFromServer(refresh, swipe, args)
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ import org.moire.ultrasonic.service.MusicService
|
||||
* Provides ViewModel which contains the list of available Artists
|
||||
*/
|
||||
class ArtistListModel(application: Application) : GenericListModel(application) {
|
||||
private val artists: MutableLiveData<List<ArtistOrIndex>> = MutableLiveData(listOf())
|
||||
private val artists: MutableLiveData<List<ArtistOrIndex>> = MutableLiveData()
|
||||
|
||||
/**
|
||||
* Retrieves all available Artists in a LiveData
|
||||
@ -39,7 +39,7 @@ class ArtistListModel(application: Application) : GenericListModel(application)
|
||||
fun getItems(refresh: Boolean, swipe: SwipeRefreshLayout): LiveData<List<ArtistOrIndex>> {
|
||||
// Don't reload the data if navigating back to the view that was active before.
|
||||
// This way, we keep the scroll position
|
||||
if (artists.value!!.isEmpty() || refresh) {
|
||||
if (artists.value?.isEmpty() != false || refresh) {
|
||||
backgroundLoadFromServer(refresh, swipe)
|
||||
}
|
||||
return artists
|
||||
|
@ -14,7 +14,7 @@ import org.moire.ultrasonic.util.Settings
|
||||
|
||||
class SearchListModel(application: Application) : GenericListModel(application) {
|
||||
|
||||
var searchResult: MutableLiveData<SearchResult?> = MutableLiveData(null)
|
||||
var searchResult: MutableLiveData<SearchResult?> = MutableLiveData()
|
||||
|
||||
override fun load(
|
||||
isOffline: Boolean,
|
||||
|
@ -19,6 +19,9 @@ import org.moire.ultrasonic.util.Util
|
||||
|
||||
/*
|
||||
* Model for retrieving different collections of tracks from the API
|
||||
*
|
||||
* TODO: Remove double data keeping in currentList/currentDirectory and use the base model liveData
|
||||
* For this refactor MusicService to replace MusicDirectories with List<Album> or List<Track>
|
||||
*/
|
||||
class TrackCollectionModel(application: Application) : GenericListModel(application) {
|
||||
|
||||
|
@ -157,7 +157,8 @@ class Downloader(
|
||||
// Add file to queue if not in one of the queues already.
|
||||
if (!download.isWorkDone &&
|
||||
!activelyDownloading.contains(download) &&
|
||||
!downloadQueue.contains(download)
|
||||
!downloadQueue.contains(download) &&
|
||||
download.shouldRetry()
|
||||
) {
|
||||
listChanged = true
|
||||
downloadQueue.add(download)
|
||||
@ -281,14 +282,18 @@ class Downloader(
|
||||
fun clearPlaylist() {
|
||||
playlist.clear()
|
||||
|
||||
val toRemove = mutableListOf<DownloadFile>()
|
||||
|
||||
// Cancel all active downloads with a high priority
|
||||
for (download in activelyDownloading) {
|
||||
if (download.priority < 100) {
|
||||
download.cancelDownload()
|
||||
activelyDownloading.remove(download)
|
||||
toRemove.add(download)
|
||||
}
|
||||
}
|
||||
|
||||
activelyDownloading.removeAll(toRemove)
|
||||
|
||||
playlistUpdateRevision++
|
||||
updateLiveData()
|
||||
}
|
||||
|
91
ultrasonic/src/main/res/drawable/ic_empty.xml
Normal file
91
ultrasonic/src/main/res/drawable/ic_empty.xml
Normal file
@ -0,0 +1,91 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="100"
|
||||
android:viewportHeight="100"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M90.365,48.085l-3.83,0l-3.859,0l-3.83,0l-3.86,0l-3.83,0l-3.86,0l0,3.86l3.86,0l3.83,0l3.86,0l3.83,0l3.859,0l3.83,0l3.861,0l0,-3.86z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M82.676,82.705h3.859v3.83h-3.859z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M82.676,13.465h3.859v3.86h-3.859z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M78.846,78.846h3.83v3.859h-3.83z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M78.846,17.325h3.83v3.83h-3.83z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M74.986,75.016h3.859v3.83h-3.859z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M74.986,21.155h3.859v3.86h-3.859z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M71.156,71.154h3.83v3.861h-3.83z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M71.156,25.015h3.83v3.83h-3.83z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M67.296,67.324h3.86v3.83h-3.86z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M67.296,28.845h3.86v3.86h-3.86z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M63.466,63.465h3.83v3.859h-3.83z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M63.466,32.705h3.83v3.83h-3.83z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M48.056,71.154l0,3.862l0,3.83l0,3.859l0,3.83l0,3.86l0,3.83l3.86,0l0,-3.83l0,-3.86l0,-3.83l0,-3.859l0,-3.83l0,-3.862l0,-3.83l-3.86,0z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M48.056,9.635l0,3.83l0,3.86l0,3.83l0,3.86l0,3.83l0,3.86l3.86,0l0,-3.86l0,-3.83l0,-3.86l0,-3.83l0,-3.86l0,-3.83l0,-3.86l-3.86,0z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M32.676,63.465h3.859v3.859h-3.859z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M32.676,32.705h3.859v3.83h-3.859z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M28.846,67.324h3.83v3.83h-3.83z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M28.846,28.845h3.83v3.86h-3.83z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M24.986,71.154h3.859v3.861h-3.859z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M24.986,25.015h3.859v3.83h-3.859z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M21.156,75.016h3.83v3.83h-3.83z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M21.156,21.155h3.83v3.86h-3.83z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M17.296,78.846h3.86v3.859h-3.86z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M17.296,17.325h3.86v3.83h-3.86z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M13.466,82.705h3.83v3.83h-3.83z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M17.296,51.945l3.86,0l3.83,0l3.86,0l3.83,0l0,-3.86l-3.83,0l-3.86,0l-3.83,0l-3.86,0l-3.83,0l-3.861,0l-3.83,0l0,3.86l3.83,0l3.861,0z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M13.466,13.465h3.83v3.86h-3.83z" />
|
||||
</vector>
|
38
ultrasonic/src/main/res/layout/empty_view.xml
Normal file
38
ultrasonic/src/main/res/layout/empty_view.xml
Normal file
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:a="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
a:layout_width="match_parent"
|
||||
a:layout_height="match_parent"
|
||||
a:visibility="gone"
|
||||
a:id="@+id/empty_list_view"
|
||||
>
|
||||
|
||||
<ImageView
|
||||
a:layout_width="100dip"
|
||||
a:layout_height="100dip"
|
||||
a:layout_marginStart="50dp"
|
||||
a:importantForAccessibility="no"
|
||||
a:layout_marginEnd="50dp"
|
||||
a:layout_marginBottom="8dp"
|
||||
a:src="@drawable/ic_empty"
|
||||
app:layout_constraintBottom_toTopOf="@+id/empty_list_text"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextView
|
||||
a:id="@+id/empty_list_text"
|
||||
a:layout_width="match_parent"
|
||||
a:layout_height="wrap_content"
|
||||
a:layout_marginTop="80dp"
|
||||
a:layout_marginBottom="20dp"
|
||||
a:drawablePadding="0dp"
|
||||
a:gravity="center"
|
||||
a:padding="12dp"
|
||||
a:text="@string/search.no_match"
|
||||
a:textAppearance="?android:attr/textAppearanceMedium"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -4,6 +4,7 @@
|
||||
a:layout_height="fill_parent"
|
||||
a:orientation="vertical">
|
||||
|
||||
<include layout="@layout/empty_view" />
|
||||
<include layout="@layout/recycler_view" />
|
||||
|
||||
</LinearLayout>
|
||||
|
@ -1,12 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:a="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<TextView
|
||||
a:id="@+id/empty_list_text"
|
||||
a:layout_width="fill_parent"
|
||||
a:layout_height="wrap_content"
|
||||
a:padding="10dip"
|
||||
a:text="@string/select_album.empty"
|
||||
a:visibility="gone" />
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
a:id="@+id/swipe_refresh_view"
|
||||
|
@ -1,29 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:a="http://schemas.android.com/apk/res/android"
|
||||
a:layout_width="fill_parent"
|
||||
a:layout_height="fill_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
a:layout_width="match_parent"
|
||||
a:layout_height="match_parent"
|
||||
a:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
a:id="@+id/empty_list_text"
|
||||
a:layout_width="fill_parent"
|
||||
a:layout_height="wrap_content"
|
||||
a:drawablePadding="0dp"
|
||||
a:gravity="center"
|
||||
a:padding="12dp"
|
||||
a:text="@string/search.no_match"
|
||||
a:textAppearance="?android:attr/textAppearanceMedium"
|
||||
a:visibility="gone" />
|
||||
<include layout="@layout/empty_view" />
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
a:id="@+id/swipe_refresh_view"
|
||||
a:layout_width="fill_parent"
|
||||
a:layout_width="match_parent"
|
||||
a:layout_height="0dip"
|
||||
a:layout_weight="1.0">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
a:id="@+id/recycler_view"
|
||||
a:layout_width="fill_parent"
|
||||
a:layout_width="match_parent"
|
||||
a:layout_height="0dip"
|
||||
a:layout_weight="1.0" />
|
||||
|
||||
|
@ -4,11 +4,7 @@
|
||||
a:layout_height="fill_parent"
|
||||
a:orientation="vertical" >
|
||||
|
||||
<View
|
||||
a:layout_width="fill_parent"
|
||||
a:layout_height="1dp"
|
||||
a:background="@color/dividerColor" />
|
||||
|
||||
<include layout="@layout/empty_view" />
|
||||
<include layout="@layout/recycler_view" />
|
||||
<include layout="@layout/album_buttons" />
|
||||
|
||||
|
@ -57,6 +57,7 @@
|
||||
<string name="delete_playlist">Do you want to delete %1$s</string>
|
||||
<string name="download.bookmark_removed" formatted="false">Bookmark removed.</string>
|
||||
<string name="download.bookmark_set_at_position" formatted="false">Bookmark set at %s.</string>
|
||||
<string name="download.empty">Nothing is downloading</string>
|
||||
<string name="playlist.empty">Playlist is empty</string>
|
||||
<string name="download.jukebox_not_authorized">Remote control is not allowed. Please enable jukebox mode in <b>Users > Settings</b> on your Subsonic server.</string>
|
||||
<string name="download.jukebox_off">Turned off remote control. Music is played on phone.</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user