package org.moire.ultrasonic.fragment import android.os.Bundle import android.view.LayoutInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.LiveData import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel import org.moire.ultrasonic.R import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.domain.Artist import org.moire.ultrasonic.domain.GenericEntry import org.moire.ultrasonic.domain.Identifiable import org.moire.ultrasonic.domain.MusicFolder import org.moire.ultrasonic.subsonic.DownloadHandler import org.moire.ultrasonic.subsonic.ImageLoaderProvider import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.Settings import org.moire.ultrasonic.util.Util import org.moire.ultrasonic.view.SelectMusicFolderView /** * An abstract Model, which can be extended to display a list of items of type T from the API * @param T: The type of data which will be used (must extend GenericEntry) * @param TA: The Adapter to use (must extend GenericRowAdapter) */ abstract class GenericListFragment> : Fragment() { internal val activeServerProvider: ActiveServerProvider by inject() internal val serverSettingsModel: ServerSettingsModel by viewModel() internal val imageLoaderProvider: ImageLoaderProvider by inject() protected val downloadHandler: DownloadHandler by inject() protected var refreshListView: SwipeRefreshLayout? = null internal var listView: RecyclerView? = null internal lateinit var viewManager: LinearLayoutManager internal var selectFolderHeader: SelectMusicFolderView? = null /** * The Adapter for the RecyclerView * Recommendation: Implement this as a lazy delegate */ internal abstract val viewAdapter: TA /** * The ViewModel to use to get the data */ open val listModel: GenericListModel by viewModels() /** * The LiveData containing the list provided by the model * Implement this as a getter */ internal lateinit var liveDataItems: LiveData> /** * The central function to pass a query to the model and return a LiveData object */ abstract fun getLiveData(args: Bundle? = null): LiveData> /** * The id of the target in the navigation graph where we should go, * after the user has clicked on an item */ protected abstract val itemClickTarget: Int /** * The id of the RecyclerView */ protected abstract val recyclerViewId: Int /** * The id of the main layout */ abstract val mainLayout: Int /** * The id of the refresh view */ abstract val refreshListId: Int /** * The observer to be called if the available music folders have changed */ @Suppress("CommentOverPrivateProperty") private val musicFolderObserver = { folders: List -> viewAdapter.setFolderList(folders, listModel.activeServer.musicFolderId) } /** * What to do when the user has modified the folder filter */ val onMusicFolderUpdate = { selectedFolderId: String? -> if (!listModel.isOffline()) { val currentSetting = listModel.activeServer currentSetting.musicFolderId = selectedFolderId serverSettingsModel.updateItem(currentSetting) } viewAdapter.notifyDataSetChanged() listModel.refresh(refreshListView!!, arguments) } /** * Whether to show the folder selector */ fun showFolderHeader(): Boolean { return listModel.showSelectFolderHeader(arguments) && !listModel.isOffline() && !Settings.shouldUseId3Tags } open fun setTitle(title: String?) { if (title == null) { FragmentTitle.setTitle( this, if (listModel.isOffline()) R.string.music_library_label_offline else R.string.music_library_label ) } else { FragmentTitle.setTitle(this, title) } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // Set the title if available setTitle(arguments?.getString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE)) // Setup refresh handler refreshListView = view.findViewById(refreshListId) refreshListView?.setOnRefreshListener { listModel.refresh(refreshListView!!, arguments) } // Populate the LiveData. This starts an API request in most cases liveDataItems = getLiveData(arguments) // Register an observer to update our UI when the data changes liveDataItems.observe(viewLifecycleOwner, { newItems -> viewAdapter.submitList(newItems) }) // Setup the Music folder handling listModel.getMusicFolders().observe(viewLifecycleOwner, musicFolderObserver) // Create a View Manager viewManager = LinearLayoutManager(this.context) // Hook up the view with the manager and the adapter listView = view.findViewById(recyclerViewId).apply { setHasFixedSize(true) layoutManager = viewManager adapter = viewAdapter } // Configure whether to show the folder header viewAdapter.folderHeaderEnabled = showFolderHeader() } @Override override fun onCreate(savedInstanceState: Bundle?) { Util.applyTheme(this.context) super.onCreate(savedInstanceState) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return inflater.inflate(mainLayout, container, false) } abstract fun onContextMenuItemSelected(menuItem: MenuItem, item: T): Boolean abstract fun onItemClick(item: T) } abstract class EntryListFragment> : GenericListFragment() { @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 } override fun onItemClick(item: T) { val bundle = Bundle() bundle.putString(Constants.INTENT_EXTRA_NAME_ID, item.id) 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)) findNavController().navigate(itemClickTarget, bundle) } }