diff --git a/detekt-baseline-debug.xml b/detekt-baseline-debug.xml index fff5d6ce..bc6e7b45 100644 --- a/detekt-baseline-debug.xml +++ b/detekt-baseline-debug.xml @@ -33,7 +33,7 @@ ComplexCondition:SelectAlbumFragment.kt$SelectAlbumFragment$enabled && !deleteEnabled && !isOffline(context) ComplexCondition:SelectAlbumFragment.kt$SelectAlbumFragment$enabled && !isOffline(context) && selection.size > pinnedCount ComplexCondition:SelectAlbumFragment.kt$SelectAlbumFragment$entry != null && !entry.isDirectory && !entry.isVideo - ComplexCondition:SelectAlbumFragment.kt$SelectAlbumFragment.<no name provided>$Util.getShouldShowAllSongsByArtist(context) && musicDirectory.findChild(allSongsId) == null && musicDirectory.getChildren(true, false).size == musicDirectory.getChildren(true, true).size + ComplexCondition:SelectAlbumModel.kt$SelectAlbumModel$Util.getShouldShowAllSongsByArtist(context) && musicDirectory.findChild(allSongsId) == null && musicDirectory.getChildren(true, false).size == musicDirectory.getChildren(true, true).size ComplexCondition:ServerSettingsModel.kt$ServerSettingsModel$url.isNullOrEmpty() || userName.isNullOrEmpty() || isMigrated ComplexCondition:SongView.kt$SongView$TextUtils.isEmpty(transcodedSuffix) || transcodedSuffix == suffix || song.isVideo && Util.getVideoPlayerType(this.context) !== VideoPlayerType.FLASH ComplexCondition:SubsonicImageLoaderProxy.kt$SubsonicImageLoaderProxy$id != null && view != null && view is ImageView @@ -49,10 +49,9 @@ ComplexMethod:MediaPlayerService.kt$MediaPlayerService$private fun setupOnSongCompletedHandler() ComplexMethod:RestErrorMapper.kt$ fun SubsonicRESTException.getLocalizedErrorMessage(context: Context): String ComplexMethod:SelectAlbumFragment.kt$SelectAlbumFragment$override fun onContextItemSelected(menuItem: MenuItem): Boolean - ComplexMethod:SelectAlbumFragment.kt$SelectAlbumFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?) ComplexMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun enableButtons() ComplexMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun updateDisplay(refresh: Boolean) - ComplexMethod:SelectAlbumFragment.kt$SelectAlbumFragment.LoadTask$protected override fun done(result: Pair<MusicDirectory, Boolean>) + ComplexMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun updateInterfaceWithEntries(musicDirectory: MusicDirectory) ComplexMethod:SelectArtistFragment.kt$SelectArtistFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?) ComplexMethod:ServerRowAdapter.kt$ServerRowAdapter$ override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View? ComplexMethod:SongView.kt$SongView$fun setSong(song: MusicDirectory.Entry, checkable: Boolean, draggable: Boolean) @@ -85,9 +84,9 @@ LargeClass:NavigationActivity.kt$NavigationActivity : AppCompatActivity LargeClass:RESTMusicService.kt$RESTMusicService : MusicService LargeClass:SelectAlbumFragment.kt$SelectAlbumFragment : Fragment - LargeClass:SelectAlbumFragment.kt$SelectAlbumFragment$LoadTask : FragmentBackgroundTask + LargeClass:SelectAlbumModel.kt$SelectAlbumModel : AndroidViewModelKoinComponent LargeClass:SelectArtistFragment.kt$SelectArtistFragment : Fragment - LargeClass:ServerSettingsModel.kt$ServerSettingsModel : ViewModel + LargeClass:ServerSettingsModel.kt$ServerSettingsModel : AndroidViewModel LargeClass:SongView.kt$SongView : UpdateViewCheckable LongMethod:APIMusicDirectoryConverter.kt$fun MusicDirectoryChild.toDomainEntity(): MusicDirectory.Entry LongMethod:ActiveServerProvider.kt$ActiveServerProvider$ fun getActiveServer(): ServerSetting @@ -140,14 +139,16 @@ LongMethod:RestErrorMapper.kt$ fun SubsonicRESTException.getLocalizedErrorMessage(context: Context): String LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$override fun onContextItemSelected(menuItem: MenuItem): Boolean LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?) + LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun createHeader( entries: List<MusicDirectory.Entry>, name: CharSequence?, songCount: Int ): View? LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun downloadBackground(save: Boolean, songs: List<MusicDirectory.Entry?>) LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun enableButtons() LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun playAll(shuffle: Boolean = false, append: Boolean = false) LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun updateDisplay(refresh: Boolean) - LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment.<no name provided>$override fun done(result: Pair<MusicDirectory, Boolean>) - LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment.<no name provided>$override fun load(service: MusicService): MusicDirectory - LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment.LoadTask$protected fun createHeader( entries: List<MusicDirectory.Entry>, name: CharSequence?, songCount: Int ): View? - LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment.LoadTask$protected override fun done(result: Pair<MusicDirectory, Boolean>) + LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun updateInterfaceWithEntries(musicDirectory: MusicDirectory) + LongMethod:SelectAlbumModel.kt$SelectAlbumModel$suspend fun getAlbum(refresh: Boolean, id: String?, name: String?, parentId: String?) + LongMethod:SelectAlbumModel.kt$SelectAlbumModel$suspend fun getAlbumList(albumListType: String, size: Int, offset: Int) + LongMethod:SelectAlbumModel.kt$SelectAlbumModel$suspend fun getArtist(refresh: Boolean, id: String?, name: String?) + LongMethod:SelectAlbumModel.kt$SelectAlbumModel$suspend fun getMusicDirectory( refresh: Boolean, id: String?, name: String?, parentId: String? ) LongMethod:SelectArtistFragment.kt$SelectArtistFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?) LongMethod:SelectArtistFragment.kt$SelectArtistFragment$private fun onArtistMenuItemSelected(menuItem: MenuItem, artist: Artist): Boolean LongMethod:ServerRowAdapter.kt$ServerRowAdapter$ override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View? @@ -204,7 +205,6 @@ MagicNumber:RESTMusicService.kt$RESTMusicService$206 MagicNumber:RESTMusicService.kt$RESTMusicService$5 MagicNumber:SelectAlbumFragment.kt$SelectAlbumFragment$10 - MagicNumber:SelectAlbumFragment.kt$SelectAlbumFragment.LoadTask$10 MagicNumber:SelectMusicFolderView.kt$SelectMusicFolderView$10 MagicNumber:SongView.kt$SongView$3 MagicNumber:SongView.kt$SongView$4 @@ -212,7 +212,6 @@ NestedBlockDepth:DownloadFile.kt$DownloadFile.DownloadTask$override fun execute() NestedBlockDepth:DownloadHandler.kt$DownloadHandler$private fun downloadRecursively( fragment: Fragment, id: String, name: String?, isShare: Boolean, isDirectory: Boolean, save: Boolean, append: Boolean, autoPlay: Boolean, shuffle: Boolean, background: Boolean, playNext: Boolean, unpin: Boolean, isArtist: Boolean ) NestedBlockDepth:MediaPlayerService.kt$MediaPlayerService$private fun setupOnSongCompletedHandler() - NestedBlockDepth:SelectAlbumFragment.kt$SelectAlbumFragment$private fun getAlbum(refresh: Boolean, id: String?, name: String?, parentId: String?) ReturnCount:ActiveServerProvider.kt$ActiveServerProvider$ fun getActiveServer(): ServerSetting ReturnCount:CommunicationErrorHandler.kt$CommunicationErrorHandler.Companion$fun getErrorMessage(error: Throwable, context: Context): String ReturnCount:FileLoggerTree.kt$FileLoggerTree$ private fun getNextLogFile() @@ -241,7 +240,6 @@ TooGenericExceptionCaught:LocalMediaPlayer.kt$LocalMediaPlayer.PositionCache$e: Exception TooGenericExceptionCaught:MediaPlayerService.kt$MediaPlayerService$e: Exception TooGenericExceptionCaught:MediaPlayerService.kt$MediaPlayerService$x: IndexOutOfBoundsException - TooGenericExceptionCaught:SelectAlbumFragment.kt$SelectAlbumFragment$exception: Exception TooGenericExceptionCaught:SongView.kt$SongView$e: Exception TooGenericExceptionCaught:SubsonicUncaughtExceptionHandler.kt$SubsonicUncaughtExceptionHandler$x: Throwable TooGenericExceptionCaught:VideoPlayer.kt$VideoPlayer$e: Exception diff --git a/detekt-baseline-release.xml b/detekt-baseline-release.xml index fff5d6ce..2e0abd55 100644 --- a/detekt-baseline-release.xml +++ b/detekt-baseline-release.xml @@ -33,7 +33,7 @@ ComplexCondition:SelectAlbumFragment.kt$SelectAlbumFragment$enabled && !deleteEnabled && !isOffline(context) ComplexCondition:SelectAlbumFragment.kt$SelectAlbumFragment$enabled && !isOffline(context) && selection.size > pinnedCount ComplexCondition:SelectAlbumFragment.kt$SelectAlbumFragment$entry != null && !entry.isDirectory && !entry.isVideo - ComplexCondition:SelectAlbumFragment.kt$SelectAlbumFragment.<no name provided>$Util.getShouldShowAllSongsByArtist(context) && musicDirectory.findChild(allSongsId) == null && musicDirectory.getChildren(true, false).size == musicDirectory.getChildren(true, true).size + ComplexCondition:SelectAlbumModel.kt$SelectAlbumModel$Util.getShouldShowAllSongsByArtist(context) && musicDirectory.findChild(allSongsId) == null && musicDirectory.getChildren(true, false).size == musicDirectory.getChildren(true, true).size ComplexCondition:ServerSettingsModel.kt$ServerSettingsModel$url.isNullOrEmpty() || userName.isNullOrEmpty() || isMigrated ComplexCondition:SongView.kt$SongView$TextUtils.isEmpty(transcodedSuffix) || transcodedSuffix == suffix || song.isVideo && Util.getVideoPlayerType(this.context) !== VideoPlayerType.FLASH ComplexCondition:SubsonicImageLoaderProxy.kt$SubsonicImageLoaderProxy$id != null && view != null && view is ImageView @@ -49,10 +49,11 @@ ComplexMethod:MediaPlayerService.kt$MediaPlayerService$private fun setupOnSongCompletedHandler() ComplexMethod:RestErrorMapper.kt$ fun SubsonicRESTException.getLocalizedErrorMessage(context: Context): String ComplexMethod:SelectAlbumFragment.kt$SelectAlbumFragment$override fun onContextItemSelected(menuItem: MenuItem): Boolean - ComplexMethod:SelectAlbumFragment.kt$SelectAlbumFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?) ComplexMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun enableButtons() ComplexMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun updateDisplay(refresh: Boolean) - ComplexMethod:SelectAlbumFragment.kt$SelectAlbumFragment.LoadTask$protected override fun done(result: Pair<MusicDirectory, Boolean>) + ComplexMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun updateInterfaceWithEntries(musicDirectory: MusicDirectory) + ComplexMethod:SelectAlbumModel.kt$SelectAlbumModel$suspend fun getAlbumList(albumListType: String, size: Int, offset: Int) + ComplexMethod:SelectAlbumModel.kt$SelectAlbumModel$suspend fun getMusicDirectory( refresh: Boolean, id: String?, name: String?, parentId: String? ) ComplexMethod:SelectArtistFragment.kt$SelectArtistFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?) ComplexMethod:ServerRowAdapter.kt$ServerRowAdapter$ override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View? ComplexMethod:SongView.kt$SongView$fun setSong(song: MusicDirectory.Entry, checkable: Boolean, draggable: Boolean) @@ -85,9 +86,9 @@ LargeClass:NavigationActivity.kt$NavigationActivity : AppCompatActivity LargeClass:RESTMusicService.kt$RESTMusicService : MusicService LargeClass:SelectAlbumFragment.kt$SelectAlbumFragment : Fragment - LargeClass:SelectAlbumFragment.kt$SelectAlbumFragment$LoadTask : FragmentBackgroundTask + LargeClass:SelectAlbumModel.kt$SelectAlbumModel : AndroidViewModelKoinComponent LargeClass:SelectArtistFragment.kt$SelectArtistFragment : Fragment - LargeClass:ServerSettingsModel.kt$ServerSettingsModel : ViewModel + LargeClass:ServerSettingsModel.kt$ServerSettingsModel : AndroidViewModel LargeClass:SongView.kt$SongView : UpdateViewCheckable LongMethod:APIMusicDirectoryConverter.kt$fun MusicDirectoryChild.toDomainEntity(): MusicDirectory.Entry LongMethod:ActiveServerProvider.kt$ActiveServerProvider$ fun getActiveServer(): ServerSetting @@ -140,14 +141,16 @@ LongMethod:RestErrorMapper.kt$ fun SubsonicRESTException.getLocalizedErrorMessage(context: Context): String LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$override fun onContextItemSelected(menuItem: MenuItem): Boolean LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?) + LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun createHeader( entries: List<MusicDirectory.Entry>, name: CharSequence?, songCount: Int ): View? LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun downloadBackground(save: Boolean, songs: List<MusicDirectory.Entry?>) LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun enableButtons() LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun playAll(shuffle: Boolean = false, append: Boolean = false) LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun updateDisplay(refresh: Boolean) - LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment.<no name provided>$override fun done(result: Pair<MusicDirectory, Boolean>) - LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment.<no name provided>$override fun load(service: MusicService): MusicDirectory - LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment.LoadTask$protected fun createHeader( entries: List<MusicDirectory.Entry>, name: CharSequence?, songCount: Int ): View? - LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment.LoadTask$protected override fun done(result: Pair<MusicDirectory, Boolean>) + LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun updateInterfaceWithEntries(musicDirectory: MusicDirectory) + LongMethod:SelectAlbumModel.kt$SelectAlbumModel$suspend fun getAlbum(refresh: Boolean, id: String?, name: String?, parentId: String?) + LongMethod:SelectAlbumModel.kt$SelectAlbumModel$suspend fun getAlbumList(albumListType: String, size: Int, offset: Int) + LongMethod:SelectAlbumModel.kt$SelectAlbumModel$suspend fun getArtist(refresh: Boolean, id: String?, name: String?) + LongMethod:SelectAlbumModel.kt$SelectAlbumModel$suspend fun getMusicDirectory( refresh: Boolean, id: String?, name: String?, parentId: String? ) LongMethod:SelectArtistFragment.kt$SelectArtistFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?) LongMethod:SelectArtistFragment.kt$SelectArtistFragment$private fun onArtistMenuItemSelected(menuItem: MenuItem, artist: Artist): Boolean LongMethod:ServerRowAdapter.kt$ServerRowAdapter$ override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View? @@ -204,7 +207,6 @@ MagicNumber:RESTMusicService.kt$RESTMusicService$206 MagicNumber:RESTMusicService.kt$RESTMusicService$5 MagicNumber:SelectAlbumFragment.kt$SelectAlbumFragment$10 - MagicNumber:SelectAlbumFragment.kt$SelectAlbumFragment.LoadTask$10 MagicNumber:SelectMusicFolderView.kt$SelectMusicFolderView$10 MagicNumber:SongView.kt$SongView$3 MagicNumber:SongView.kt$SongView$4 @@ -212,7 +214,6 @@ NestedBlockDepth:DownloadFile.kt$DownloadFile.DownloadTask$override fun execute() NestedBlockDepth:DownloadHandler.kt$DownloadHandler$private fun downloadRecursively( fragment: Fragment, id: String, name: String?, isShare: Boolean, isDirectory: Boolean, save: Boolean, append: Boolean, autoPlay: Boolean, shuffle: Boolean, background: Boolean, playNext: Boolean, unpin: Boolean, isArtist: Boolean ) NestedBlockDepth:MediaPlayerService.kt$MediaPlayerService$private fun setupOnSongCompletedHandler() - NestedBlockDepth:SelectAlbumFragment.kt$SelectAlbumFragment$private fun getAlbum(refresh: Boolean, id: String?, name: String?, parentId: String?) ReturnCount:ActiveServerProvider.kt$ActiveServerProvider$ fun getActiveServer(): ServerSetting ReturnCount:CommunicationErrorHandler.kt$CommunicationErrorHandler.Companion$fun getErrorMessage(error: Throwable, context: Context): String ReturnCount:FileLoggerTree.kt$FileLoggerTree$ private fun getNextLogFile() @@ -241,7 +242,6 @@ TooGenericExceptionCaught:LocalMediaPlayer.kt$LocalMediaPlayer.PositionCache$e: Exception TooGenericExceptionCaught:MediaPlayerService.kt$MediaPlayerService$e: Exception TooGenericExceptionCaught:MediaPlayerService.kt$MediaPlayerService$x: IndexOutOfBoundsException - TooGenericExceptionCaught:SelectAlbumFragment.kt$SelectAlbumFragment$exception: Exception TooGenericExceptionCaught:SongView.kt$SongView$e: Exception TooGenericExceptionCaught:SubsonicUncaughtExceptionHandler.kt$SubsonicUncaughtExceptionHandler$x: Throwable TooGenericExceptionCaught:VideoPlayer.kt$VideoPlayer$e: Exception diff --git a/detekt-config.yml b/detekt-config.yml index 34c3446c..855e94ac 100644 --- a/detekt-config.yml +++ b/detekt-config.yml @@ -49,8 +49,6 @@ complexity: thresholdInFiles: 20 thresholdInClasses: 20 thresholdInInterfaces: 20 - ComplexCondition: - threshold: 3 LabeledExpression: active: false diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/EntryAdapter.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/EntryAdapter.java index 5f655a7f..bd05bff8 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/EntryAdapter.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/EntryAdapter.java @@ -33,6 +33,8 @@ import org.moire.ultrasonic.util.ImageLoader; import java.util.List; /** + * This is the adapter for the display of a single list item (song, album, etc) + * * @author Sindre Mehus */ public class EntryAdapter extends ArrayAdapter diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/UpdateView.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/UpdateView.java index 5236726b..96338d01 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/UpdateView.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/UpdateView.java @@ -14,6 +14,11 @@ import java.util.ArrayList; import java.util.Collection; import java.util.WeakHashMap; +/** + * A View that is periodically refreshed + * @deprecated + * Use LiveData to ensure that the content is up-to-date + **/ public class UpdateView extends LinearLayout { private static final WeakHashMap INSTANCES = new WeakHashMap(); diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/AppPermanentStorageModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/AppPermanentStorageModule.kt index e22dd105..4aab32b1 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/AppPermanentStorageModule.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/AppPermanentStorageModule.kt @@ -31,5 +31,5 @@ val appPermanentStorage = module { single { get().serverSettingDao() } - viewModel { ServerSettingsModel(get(), get(), androidContext()) } + viewModel { ServerSettingsModel(get(), get(), get()) } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SelectAlbumFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SelectAlbumFragment.kt index 6a9043aa..bc059dfa 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SelectAlbumFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SelectAlbumFragment.kt @@ -1,8 +1,13 @@ +/* + * SelectAlbumFragment.kt + * Copyright (C) 2009-2021 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. + */ + package org.moire.ultrasonic.fragment import android.os.Bundle -import android.os.Handler -import android.os.Looper import android.view.ContextMenu import android.view.ContextMenu.ContextMenuInfo import android.view.LayoutInflater @@ -12,40 +17,30 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.AdapterView.AdapterContextMenuInfo -import android.widget.AdapterView.OnItemClickListener -import android.widget.AdapterView.OnItemLongClickListener import android.widget.ImageView import android.widget.ListView import android.widget.TextView import androidx.fragment.app.Fragment -import androidx.lifecycle.MutableLiveData +import androidx.fragment.app.viewModels import androidx.lifecycle.Observer import androidx.lifecycle.viewModelScope import androidx.navigation.Navigation import androidx.swiperefreshlayout.widget.SwipeRefreshLayout -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener import java.security.SecureRandom import java.util.Collections -import java.util.LinkedList import java.util.Random -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import org.koin.android.ext.android.inject import org.koin.android.viewmodel.ext.android.viewModel +import org.koin.core.component.KoinApiExtension import org.moire.ultrasonic.R -import org.moire.ultrasonic.api.subsonic.models.AlbumListType import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.domain.MusicFolder import org.moire.ultrasonic.fragment.FragmentTitle.Companion.getTitle import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle -import org.moire.ultrasonic.service.CommunicationErrorHandler import org.moire.ultrasonic.service.MediaPlayerController -import org.moire.ultrasonic.service.MusicService -import org.moire.ultrasonic.service.MusicServiceFactory -import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService import org.moire.ultrasonic.subsonic.DownloadHandler import org.moire.ultrasonic.subsonic.ImageLoaderProvider import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker @@ -55,7 +50,6 @@ import org.moire.ultrasonic.util.AlbumHeader import org.moire.ultrasonic.util.CancellationToken import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.EntryByDiscAndTrackComparator -import org.moire.ultrasonic.util.FragmentBackgroundTask import org.moire.ultrasonic.util.Util import org.moire.ultrasonic.view.AlbumView import org.moire.ultrasonic.view.EntryAdapter @@ -65,10 +59,11 @@ import timber.log.Timber /** * Displays a group of playable media from the library, which can be an Album, a Playlist, etc. + * TODO: Break up this class into smaller more specific classes, extending a base class if necessary */ +@KoinApiExtension class SelectAlbumFragment : Fragment() { - private val allSongsId = "-1" private var refreshAlbumListView: SwipeRefreshLayout? = null private var albumListView: ListView? = null private var header: View? = null @@ -88,10 +83,6 @@ class SelectAlbumFragment : Fragment() { private var shareButtonVisible = false private var playAllButton: MenuItem? = null private var shareButton: MenuItem? = null - private var showHeader = true - private var showSelectFolderHeader = false - private val random: Random = SecureRandom() - private val musicFolders: MutableLiveData> = MutableLiveData() private val mediaPlayerController: MediaPlayerController by inject() private val videoPlayer: VideoPlayer by inject() @@ -101,7 +92,11 @@ class SelectAlbumFragment : Fragment() { private val shareHandler: ShareHandler by inject() private var cancellationToken: CancellationToken? = null private val activeServerProvider: ActiveServerProvider by inject() + private val serverSettingsModel: ServerSettingsModel by viewModel() + private val model: SelectAlbumModel by viewModels() + + private val random: Random = SecureRandom() override fun onCreate(savedInstanceState: Bundle?) { Util.applyTheme(this.context) @@ -125,12 +120,9 @@ class SelectAlbumFragment : Fragment() { refreshAlbumListView = view.findViewById(R.id.select_album_entries_refresh) albumListView = view.findViewById(R.id.select_album_entries_list) - refreshAlbumListView!!.setOnRefreshListener( - OnRefreshListener - { - updateDisplay(true) - } - ) + refreshAlbumListView!!.setOnRefreshListener { + updateDisplay(true) + } header = LayoutInflater.from(context).inflate( R.layout.select_album_header, albumListView, @@ -138,75 +130,53 @@ class SelectAlbumFragment : Fragment() { ) selectFolderHeader = SelectMusicFolderView( - requireContext(), view as ViewGroup, - { selectedFolderId -> - if (!ActiveServerProvider.isOffline(context)) { - val currentSetting = activeServerProvider.getActiveServer() - currentSetting.musicFolderId = selectedFolderId - serverSettingsModel.updateItem(currentSetting) - } - this.updateDisplay(true) + requireContext(), view as ViewGroup + ) { selectedFolderId -> + if (!isOffline(context)) { + val currentSetting = activeServerProvider.getActiveServer() + currentSetting.musicFolderId = selectedFolderId + serverSettingsModel.updateItem(currentSetting) } - ) - musicFolders.observe( - viewLifecycleOwner, - Observer { changedFolders -> - if (changedFolders != null) { - selectFolderHeader!!.setData( - activeServerProvider.getActiveServer().musicFolderId, - changedFolders + this.updateDisplay(true) + } + + model.musicFolders.observe(viewLifecycleOwner, musicFolderObserver) + model.currentDirectory.observe(viewLifecycleOwner, defaultObserver) + model.songsForGenre.observe(viewLifecycleOwner, songsForGenreObserver) + model.albumList.observe(viewLifecycleOwner, albumListObserver) + + albumListView!!.choiceMode = ListView.CHOICE_MODE_MULTIPLE + albumListView!!.setOnItemClickListener { parent, theView, position, _ -> + if (position >= 0) { + val entry = parent.getItemAtPosition(position) as MusicDirectory.Entry? + if (entry != null && entry.isDirectory) { + val bundle = Bundle() + bundle.putString(Constants.INTENT_EXTRA_NAME_ID, entry.id) + bundle.putBoolean(Constants.INTENT_EXTRA_NAME_IS_ALBUM, entry.isDirectory) + bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, entry.title) + bundle.putString(Constants.INTENT_EXTRA_NAME_PARENT_ID, entry.parent) + Navigation.findNavController(theView).navigate( + R.id.selectAlbumFragment, + bundle ) + } else if (entry != null && entry.isVideo) { + videoPlayer.playVideo(requireContext(), entry) + } else { + enableButtons() } } - ) + } - albumListView!!.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE) - albumListView!!.setOnItemClickListener( - OnItemClickListener - { parent, theView, position, _ -> - if (position >= 0) { - val entry = parent.getItemAtPosition(position) as MusicDirectory.Entry? - if (entry != null && entry.isDirectory) { - val bundle = Bundle() - bundle.putString(Constants.INTENT_EXTRA_NAME_ID, entry.id) - bundle.putBoolean(Constants.INTENT_EXTRA_NAME_IS_ALBUM, entry.isDirectory) - bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, entry.title) - bundle.putString(Constants.INTENT_EXTRA_NAME_PARENT_ID, entry.parent) - Navigation.findNavController(theView).navigate( - R.id.selectAlbumFragment, - bundle - ) - } else if (entry != null && entry.isVideo) { - videoPlayer.playVideo(requireContext(), entry) - } else { - enableButtons() - } - } + albumListView!!.setOnItemLongClickListener { _, theView, _, _ -> + if (theView is AlbumView) { + return@setOnItemLongClickListener false } - ) - - // TODO Long click on an item will first try to maximize / collapse the item, even when it - // fits inside the TextView. The context menu is only displayed on the second long click... - // This may be improved somehow, e.g. checking first if the texts fit - albumListView!!.setOnItemLongClickListener( - OnItemLongClickListener - { _, theView, _, _ -> - if (theView is AlbumView) { - val albumView = theView - if (!albumView.isMaximized) { - albumView.maximizeOrMinimize() - return@OnItemLongClickListener true - } else { - return@OnItemLongClickListener false - } - } - if (theView is SongView) { - theView.maximizeOrMinimize() - return@OnItemLongClickListener true - } - false + if (theView is SongView) { + theView.maximizeOrMinimize() + return@setOnItemLongClickListener true } - ) + return@setOnItemLongClickListener false + } selectButton = view.findViewById(R.id.select_album_select) playNowButton = view.findViewById(R.id.select_album_play_now) @@ -219,63 +189,39 @@ class SelectAlbumFragment : Fragment() { moreButton = view.findViewById(R.id.select_album_more) emptyView = TextView(requireContext()) - selectButton!!.setOnClickListener( - View.OnClickListener - { - selectAllOrNone() - } - ) - playNowButton!!.setOnClickListener( - View.OnClickListener - { - playNow(false) - } - ) - playNextButton!!.setOnClickListener( - View.OnClickListener - { - downloadHandler.download( - this@SelectAlbumFragment, true, - false, false, true, false, - getSelectedSongs(albumListView) - ) - selectAll(false, false) - } - ) - playLastButton!!.setOnClickListener( - View.OnClickListener - { - playNow(true) - } - ) - pinButton!!.setOnClickListener( - View.OnClickListener - { - downloadBackground(true) - selectAll(false, false) - } - ) - unpinButton!!.setOnClickListener( - View.OnClickListener - { - unpin() - selectAll(false, false) - } - ) - downloadButton!!.setOnClickListener( - View.OnClickListener - { - downloadBackground(false) - selectAll(false, false) - } - ) - deleteButton!!.setOnClickListener( - View.OnClickListener - { - delete() - selectAll(false, false) - } - ) + selectButton!!.setOnClickListener { + selectAllOrNone() + } + playNowButton!!.setOnClickListener { + playNow(false) + } + playNextButton!!.setOnClickListener { + downloadHandler.download( + this@SelectAlbumFragment, append = true, + save = false, autoPlay = false, playNext = true, shuffle = false, + songs = getSelectedSongs(albumListView) + ) + selectAll(selected = false, toast = false) + } + playLastButton!!.setOnClickListener { + playNow(true) + } + pinButton!!.setOnClickListener { + downloadBackground(true) + selectAll(selected = false, toast = false) + } + unpinButton!!.setOnClickListener { + unpin() + selectAll(selected = false, toast = false) + } + downloadButton!!.setOnClickListener { + downloadBackground(false) + selectAll(selected = false, toast = false) + } + deleteButton!!.setOnClickListener { + delete() + selectAll(selected = false, toast = false) + } registerForContextMenu(albumListView!!) setHasOptionsMenu(true) @@ -312,57 +258,57 @@ class SelectAlbumFragment : Fragment() { Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0 ) - backgroundLoadMusicFolders(refresh) - - if (playlistId != null) { - getPlaylist(playlistId, playlistName) - } else if (podcastChannelId != null) { - getPodcastEpisodes(podcastChannelId) - } else if (shareId != null) { - getShare(shareId, shareName) - } else if (albumListType != null) { - getAlbumList(albumListType, albumListTitle, albumListSize, albumListOffset) - } else if (genreName != null) { - getSongsForGenre(genreName, albumListSize, albumListOffset) - } else if (getStarredTracks != 0) { - starred - } else if (getVideos != 0) { - getVideos(refresh) - } else if (getRandomTracks != 0) { - getRandom(albumListSize) - } else { - if (!isOffline(activity) && Util.getShouldUseId3Tags(activity)) { - if (isAlbum) { - getAlbum(refresh, id, name, parentId) - } else { - getArtist(refresh, id, name) - } - } else { - getMusicDirectory(refresh, id, name, parentId) - } + fun setTitle(name: String?) { + setTitle(this@SelectAlbumFragment, name) } - } - private fun backgroundLoadMusicFolders(refresh: Boolean) { - serverSettingsModel.viewModelScope.launch { + fun setTitle(name: Int) { + setTitle(this@SelectAlbumFragment, name) + } + + model.viewModelScope.launch { refreshAlbumListView!!.isRefreshing = true - loadMusicFolders(refresh) - refreshAlbumListView!!.isRefreshing = false - } - } - private suspend fun loadMusicFolders(refresh: Boolean) { - withContext(Dispatchers.IO) { - if (!isOffline(context)) { - val musicService = MusicServiceFactory.getMusicService(requireContext()) - try { - musicFolders.postValue(musicService.getMusicFolders(refresh, context)) - } catch (exception: Exception) { - Handler(Looper.getMainLooper()).post { - CommunicationErrorHandler.handleError(exception, requireContext()) + model.getMusicFolders(refresh) + + if (playlistId != null) { + setTitle(playlistName) + model.getPlaylist(playlistId, playlistName) + } else if (podcastChannelId != null) { + setTitle(getString(R.string.podcasts_label)) + model.getPodcastEpisodes(podcastChannelId) + } else if (shareId != null) { + setTitle(shareName) + model.getShare(shareId) + } else if (albumListType != null) { + setTitle(albumListTitle) + model.getAlbumList(albumListType, albumListSize, albumListOffset) + } else if (genreName != null) { + setTitle(genreName) + model.getSongsForGenre(genreName, albumListSize, albumListOffset) + } else if (getStarredTracks != 0) { + setTitle(getString(R.string.main_songs_starred)) + model.getStarred() + } else if (getVideos != 0) { + setTitle(R.string.main_videos) + model.getVideos(refresh) + } else if (getRandomTracks != 0) { + setTitle(R.string.main_songs_random) + model.getRandom(albumListSize) + } else { + setTitle(name) + if (!isOffline(activity) && Util.getShouldUseId3Tags(activity)) { + if (isAlbum) { + model.getAlbum(refresh, id, name, parentId) + } else { + model.getArtist(refresh, id, name) } + } else { + model.getMusicDirectory(refresh, id, name, parentId) } } + + refreshAlbumListView!!.isRefreshing = false } } @@ -398,43 +344,64 @@ class SelectAlbumFragment : Fragment() { val entryId = entry.id - val itemId = menuItem.itemId - if (itemId == R.id.album_menu_play_now) { - downloadHandler.downloadRecursively( - this, entryId, false, false, true, false, false, false, false, false - ) - } else if (itemId == R.id.album_menu_play_next) { - downloadHandler.downloadRecursively( - this, entryId, false, false, false, false, false, true, false, false - ) - } else if (itemId == R.id.album_menu_play_last) { - downloadHandler.downloadRecursively( - this, entryId, false, true, false, false, false, false, false, false - ) - } else if (itemId == R.id.album_menu_pin) { - downloadHandler.downloadRecursively( - this, entryId, true, true, false, false, false, false, false, false - ) - } else if (itemId == R.id.album_menu_unpin) { - downloadHandler.downloadRecursively( - this, entryId, false, false, false, false, false, false, true, false - ) - } else if (itemId == R.id.album_menu_download) { - downloadHandler.downloadRecursively( - this, entryId, false, false, false, false, true, false, false, false - ) - } else if (itemId == R.id.select_album_play_all) { - playAll() - } else if (itemId == R.id.menu_item_share) { - val entries: MutableList = ArrayList(1) - entries.add(entry) - shareHandler.createShare( - this, entries, refreshAlbumListView, - cancellationToken!! - ) - return true - } else { - return super.onContextItemSelected(menuItem) + when (menuItem.itemId) { + R.id.album_menu_play_now -> { + downloadHandler.downloadRecursively( + this, entryId, save = false, append = false, + autoPlay = true, shuffle = false, background = false, + playNext = false, unpin = false, isArtist = false + ) + } + R.id.album_menu_play_next -> { + downloadHandler.downloadRecursively( + this, entryId, save = false, append = false, + autoPlay = false, shuffle = false, background = false, + playNext = true, unpin = false, isArtist = false + ) + } + R.id.album_menu_play_last -> { + downloadHandler.downloadRecursively( + this, entryId, save = false, append = true, + autoPlay = false, shuffle = false, background = false, + playNext = false, unpin = false, isArtist = false + ) + } + R.id.album_menu_pin -> { + downloadHandler.downloadRecursively( + this, entryId, save = true, append = true, + autoPlay = false, shuffle = false, background = false, + playNext = false, unpin = false, isArtist = false + ) + } + R.id.album_menu_unpin -> { + downloadHandler.downloadRecursively( + this, entryId, save = false, append = false, + autoPlay = false, shuffle = false, background = false, + playNext = false, unpin = true, isArtist = false + ) + } + R.id.album_menu_download -> { + downloadHandler.downloadRecursively( + this, entryId, save = false, append = false, + autoPlay = false, shuffle = false, background = true, + playNext = false, unpin = false, isArtist = false + ) + } + R.id.select_album_play_all -> { + playAll() + } + R.id.menu_item_share -> { + val entries: MutableList = ArrayList(1) + entries.add(entry) + shareHandler.createShare( + this, entries, refreshAlbumListView, + cancellationToken!! + ) + return true + } + else -> { + return super.onContextItemSelected(menuItem) + } } return true } @@ -483,12 +450,12 @@ class SelectAlbumFragment : Fragment() { private fun playNow(append: Boolean) { val selectedSongs = getSelectedSongs(albumListView) - if (!selectedSongs.isEmpty()) { + if (selectedSongs.isNotEmpty()) { downloadHandler.download( - this, append, false, !append, false, - false, selectedSongs + this, append, false, !append, playNext = false, + shuffle = false, songs = selectedSongs ) - selectAll(false, false) + selectAll(selected = false, toast = false) } else { playAll(false, append) } @@ -511,375 +478,18 @@ class SelectAlbumFragment : Fragment() { if (hasSubFolders && id != null) { downloadHandler.downloadRecursively( this, id, false, append, !append, - shuffle, false, false, false, isArtist + shuffle, background = false, playNext = false, unpin = false, isArtist = isArtist ) } else { - selectAll(true, false) + selectAll(selected = true, toast = false) downloadHandler.download( this, append, false, !append, false, shuffle, getSelectedSongs(albumListView) ) - selectAll(false, false) + selectAll(selected = false, toast = false) } } - private fun getMusicDirectory(refresh: Boolean, id: String?, name: String?, parentId: String?) { - setTitle(this, name) - - object : LoadTask() { - override fun load(service: MusicService): MusicDirectory { - var root = MusicDirectory() - - if (allSongsId == id) { - val musicDirectory = service.getMusicDirectory(parentId, name, refresh, context) - - val songs: MutableList = LinkedList() - getSongsRecursively(musicDirectory, songs) - - for (song in songs) { - if (!song.isDirectory) { - root.addChild(song) - } - } - } else { - val musicDirectory = service.getMusicDirectory(id, name, refresh, context) - - if (Util.getShouldShowAllSongsByArtist(context) && - musicDirectory.findChild(allSongsId) == null && - musicDirectory.getChildren(true, false).size == - musicDirectory.getChildren(true, true).size - ) { - val allSongs = MusicDirectory.Entry() - - allSongs.isDirectory = true - allSongs.artist = name - allSongs.parent = id - allSongs.id = allSongsId - allSongs.title = String.format( - resources.getString(R.string.select_album_all_songs), name - ) - - root.addChild(allSongs) - root.addAll(musicDirectory.getChildren()) - } else { - root = musicDirectory - } - } - return root - } - - private fun getSongsRecursively( - parent: MusicDirectory, - songs: MutableList - ) { - for (song in parent.getChildren(false, true)) { - if (!song.isVideo && !song.isDirectory) { - songs.add(song) - } - } - - val musicService = getMusicService(context!!) - - for ((id1, _, _, title) in parent.getChildren(true, false)) { - var root: MusicDirectory - - if (allSongsId != id1) { - root = musicService.getMusicDirectory(id1, title, false, context) - - getSongsRecursively(root, songs) - } - } - } - }.execute() - } - - private fun getArtist(refresh: Boolean, id: String?, name: String?) { - setTitle(this, name) - - object : LoadTask() { - override fun load(service: MusicService): MusicDirectory { - - var root = MusicDirectory() - - val musicDirectory = service.getArtist(id, name, refresh, context) - - if (Util.getShouldShowAllSongsByArtist(context) && - musicDirectory.findChild(allSongsId) == null && - musicDirectory.getChildren(true, false).size == - musicDirectory.getChildren(true, true).size - ) { - val allSongs = MusicDirectory.Entry() - - allSongs.isDirectory = true - allSongs.artist = name - allSongs.parent = id - allSongs.id = allSongsId - allSongs.title = String.format( - resources.getString(R.string.select_album_all_songs), name - ) - - root.addFirst(allSongs) - root.addAll(musicDirectory.getChildren()) - } else { - root = musicDirectory - } - return root - } - }.execute() - } - - private fun getAlbum(refresh: Boolean, id: String?, name: String?, parentId: String?) { - setTitle(this, name) - - object : LoadTask() { - override fun load(service: MusicService): MusicDirectory { - - val musicDirectory: MusicDirectory - - musicDirectory = if (allSongsId == id) { - val root = MusicDirectory() - - val songs: MutableCollection = LinkedList() - getSongsForArtist(parentId, songs) - - for (song in songs) { - if (!song.isDirectory) { - root.addChild(song) - } - } - root - } else { - service.getAlbum(id, name, refresh, context) - } - return musicDirectory - } - - private fun getSongsForArtist( - id: String?, - songs: MutableCollection - ) { - - val musicService = getMusicService(context!!) - val artist = musicService.getArtist(id, "", false, context) - - for ((id1) in artist.getChildren()) { - if (allSongsId != id1) { - val albumDirectory = musicService.getAlbum(id1, "", false, context) - - for (song in albumDirectory.getChildren()) { - if (!song.isVideo) { - songs.add(song) - } - } - } - } - } - }.execute() - } - - private fun getSongsForGenre(genre: String, count: Int, offset: Int) { - setTitle(this, genre) - - object : LoadTask() { - override fun load(service: MusicService): MusicDirectory { - return service.getSongsByGenre(genre, count, offset, context) - } - - override fun done(result: Pair) { - // Hide more button when results are less than album list size - if (result.first.getChildren().size < arguments!!.getInt( - Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0 - ) - ) { - moreButton!!.visibility = View.GONE - } else { - moreButton!!.visibility = View.VISIBLE - } - - moreButton!!.setOnClickListener { - val theGenre = arguments!!.getString(Constants.INTENT_EXTRA_NAME_GENRE_NAME) - val size = arguments!!.getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0) - val theOffset = arguments!!.getInt( - Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0 - ) + size - val bundle = Bundle() - bundle.putString(Constants.INTENT_EXTRA_NAME_GENRE_NAME, theGenre) - bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, size) - bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, theOffset) - Navigation.findNavController(view!!).navigate(R.id.selectAlbumFragment, bundle) - } - - super.done(result) - } - }.execute() - } - - private val starred: Unit - get() { - setTitle(this, R.string.main_songs_starred) - - object : LoadTask() { - override fun load(service: MusicService): MusicDirectory { - return if (Util.getShouldUseId3Tags(context)) - Util.getSongsFromSearchResult(service.getStarred2(context)) - else - Util.getSongsFromSearchResult(service.getStarred(context)) - } - }.execute() - } - - private fun getVideos(refresh: Boolean) { - showHeader = false - - setTitle(this, R.string.main_videos) - - object : LoadTask() { - override fun load(service: MusicService): MusicDirectory { - return service.getVideos(refresh, context) - } - }.execute() - } - - private fun getRandom(size: Int) { - setTitle(this, R.string.main_songs_random) - - object : LoadTask() { - override fun sortableCollection(): Boolean { - return false - } - - override fun load(service: MusicService): MusicDirectory { - return service.getRandomSongs(size, context) - } - }.execute() - } - - private fun getPlaylist(playlistId: String, playlistName: String?) { - - setTitle(this, playlistName) - - object : LoadTask() { - override fun load(service: MusicService): MusicDirectory { - return service.getPlaylist(playlistId, playlistName, context) - } - }.execute() - } - - private fun getPodcastEpisodes(podcastChannelId: String) { - - setTitle(this, R.string.podcasts_label) - - object : LoadTask() { - override fun load(service: MusicService): MusicDirectory { - return service.getPodcastEpisodes(podcastChannelId, context) - } - }.execute() - } - - private fun getShare(shareId: String, shareName: CharSequence?) { - - setTitle(this, shareName) - // setActionBarSubtitle(shareName); - - object : LoadTask() { - override fun load(service: MusicService): MusicDirectory { - val shares = service.getShares(true, context) - - val md = MusicDirectory() - - for (share in shares) { - if (share.id == shareId) { - for (entry in share.getEntries()) { - md.addChild(entry) - } - break - } - } - return md - } - }.execute() - } - - private fun getAlbumList(albumListType: String, albumListTitle: Int, size: Int, offset: Int) { - - showHeader = false - showSelectFolderHeader = !isOffline(context) && !Util.getShouldUseId3Tags(context) && - ( - (albumListType == AlbumListType.SORTED_BY_NAME.toString()) || - (albumListType == AlbumListType.SORTED_BY_ARTIST.toString()) - ) - - setTitle(this, albumListTitle) - // setActionBarSubtitle(albumListTitle); - - object : LoadTask() { - override fun sortableCollection(): Boolean { - return albumListType != "newest" && albumListType != "random" && - albumListType != "highest" && albumListType != "recent" && - albumListType != "frequent" - } - - override fun load(service: MusicService): MusicDirectory { - val musicFolderId = if (showSelectFolderHeader) { - this@SelectAlbumFragment.activeServerProvider.getActiveServer().musicFolderId - } else { - null - } - return if (Util.getShouldUseId3Tags(context)) - service.getAlbumList2(albumListType, size, offset, musicFolderId, context) - else - service.getAlbumList(albumListType, size, offset, musicFolderId, context) - } - - override fun done(result: Pair) { - if (!result.first.getChildren().isEmpty()) { - pinButton!!.visibility = View.GONE - unpinButton!!.visibility = View.GONE - downloadButton!!.visibility = View.GONE - deleteButton!!.visibility = View.GONE - - // Hide more button when results are less than album list size - if (result.first.getChildren().size < arguments!!.getInt( - Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0 - ) - ) { - moreButton!!.visibility = View.GONE - } else { - moreButton!!.visibility = View.VISIBLE - moreButton!!.setOnClickListener { - val theAlbumListTitle = arguments!!.getInt( - Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, 0 - ) - val type = arguments!!.getString( - Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE - ) - val theSize = arguments!!.getInt( - Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0 - ) - val theOffset = arguments!!.getInt( - Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0 - ) + theSize - - val bundle = Bundle() - bundle.putInt( - Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, theAlbumListTitle - ) - bundle.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, type) - bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, theSize) - bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, theOffset) - Navigation.findNavController(view!!).navigate( - R.id.selectAlbumFragment, bundle - ) - } - } - } else { - moreButton!!.visibility = View.GONE - } - super.done(result) - } - }.execute() - } - private fun selectAllOrNone() { var someUnselected = false val count = albumListView!!.count @@ -920,7 +530,7 @@ class SelectAlbumFragment : Fragment() { private fun enableButtons() { val selection = getSelectedSongs(albumListView) - val enabled = !selection.isEmpty() + val enabled = selection.isNotEmpty() var unpinEnabled = false var deleteEnabled = false @@ -956,7 +566,7 @@ class SelectAlbumFragment : Fragment() { var songs = getSelectedSongs(albumListView) if (songs.isEmpty()) { - selectAll(true, false) + selectAll(selected = true, toast = false) songs = getSelectedSongs(albumListView) } @@ -991,7 +601,7 @@ class SelectAlbumFragment : Fragment() { var songs = getSelectedSongs(albumListView) if (songs.isEmpty()) { - selectAll(true, false) + selectAll(selected = true, toast = false) songs = getSelectedSongs(albumListView) } @@ -1009,212 +619,281 @@ class SelectAlbumFragment : Fragment() { mediaPlayerController.unpin(songs) } - private abstract inner class LoadTask : FragmentBackgroundTask>( - this@SelectAlbumFragment.activity, true, refreshAlbumListView, - cancellationToken - ) { - - protected abstract fun load(service: MusicService): MusicDirectory - protected open fun sortableCollection(): Boolean { - return true + private val musicFolderObserver = Observer> { changedFolders -> + if (changedFolders != null) { + selectFolderHeader!!.setData( + activeServerProvider.getActiveServer().musicFolderId, + changedFolders + ) } + } - override fun doInBackground(): Pair { - val musicService = getMusicService(context!!) - val dir = load(musicService) - val valid = musicService.isLicenseValid(context) - return Pair(dir, valid) - } + private val albumListObserver = Observer { musicDirectory -> + if (musicDirectory.getChildren().isNotEmpty()) { + pinButton!!.visibility = View.GONE + unpinButton!!.visibility = View.GONE + downloadButton!!.visibility = View.GONE + deleteButton!!.visibility = View.GONE - protected override fun done(result: Pair) { - val musicDirectory = result.first - val entries = musicDirectory.getChildren() - - if (sortableCollection() && Util.getShouldSortByDisc(context)) { - Collections.sort(entries, EntryByDiscAndTrackComparator()) - } - - var allVideos = true - var songCount = 0 - - for (entry in entries) { - if (!entry.isVideo) { - allVideos = false - } - if (!entry.isDirectory) { - songCount++ - } - } - - val listSize = arguments!!.getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0) - - if (songCount > 0) { - if (showHeader) { - val intentAlbumName = arguments!!.getString(Constants.INTENT_EXTRA_NAME_NAME) - val directoryName = musicDirectory.name - val header = createHeader( - entries, intentAlbumName ?: directoryName, - songCount - ) - if (header != null && albumListView!!.headerViewsCount == 0) { - albumListView!!.addHeaderView(header, null, false) - } - } - - pinButton!!.visibility = View.VISIBLE - unpinButton!!.visibility = View.VISIBLE - downloadButton!!.visibility = View.VISIBLE - deleteButton!!.visibility = View.VISIBLE - selectButton!!.visibility = if (allVideos) View.GONE else View.VISIBLE - playNowButton!!.visibility = View.VISIBLE - playNextButton!!.visibility = View.VISIBLE - playLastButton!!.visibility = View.VISIBLE - - if (listSize == 0 || songCount < listSize) { - moreButton!!.visibility = View.GONE - } else { - moreButton!!.visibility = View.VISIBLE - if (arguments!!.getInt(Constants.INTENT_EXTRA_NAME_RANDOM, 0) > 0) { - moreButton!!.setOnClickListener { - val offset = arguments!!.getInt( - Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0 - ) + listSize - val bundle = Bundle() - bundle.putInt(Constants.INTENT_EXTRA_NAME_RANDOM, 1) - bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, listSize) - bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, offset) - Navigation.findNavController(view!!).navigate( - R.id.selectAlbumFragment, bundle - ) - } - } - } + // Hide more button when results are less than album list size + if (musicDirectory.getChildren().size < requireArguments().getInt( + Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0 + ) + ) { + moreButton!!.visibility = View.GONE } else { - if (showSelectFolderHeader) { - if (albumListView!!.headerViewsCount == 0) { - albumListView!!.addHeaderView(selectFolderHeader!!.itemView, null, false) + moreButton!!.visibility = View.VISIBLE + moreButton!!.setOnClickListener { + val theAlbumListTitle = requireArguments().getInt( + Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, 0 + ) + val type = requireArguments().getString( + Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE + ) + val theSize = requireArguments().getInt( + Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0 + ) + val theOffset = requireArguments().getInt( + Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0 + ) + theSize + + val bundle = Bundle() + bundle.putInt( + Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, theAlbumListTitle + ) + bundle.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, type) + bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, theSize) + bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, theOffset) + Navigation.findNavController(requireView()).navigate( + R.id.selectAlbumFragment, bundle + ) + } + } + } else { + moreButton!!.visibility = View.GONE + } + + updateInterfaceWithEntries(musicDirectory) + } + + private val songsForGenreObserver = Observer { musicDirectory -> + + // Hide more button when results are less than album list size + if (musicDirectory.getChildren().size < requireArguments().getInt( + Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0 + ) + ) { + moreButton!!.visibility = View.GONE + } else { + moreButton!!.visibility = View.VISIBLE + } + + moreButton!!.setOnClickListener { + val theGenre = requireArguments().getString(Constants.INTENT_EXTRA_NAME_GENRE_NAME) + val size = requireArguments().getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0) + val theOffset = requireArguments().getInt( + Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0 + ) + size + val bundle = Bundle() + bundle.putString(Constants.INTENT_EXTRA_NAME_GENRE_NAME, theGenre) + bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, size) + bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, theOffset) + Navigation.findNavController(requireView()).navigate(R.id.selectAlbumFragment, bundle) + } + + updateInterfaceWithEntries(musicDirectory) + } + + private val defaultObserver = Observer(this::updateInterfaceWithEntries) + + private fun updateInterfaceWithEntries(musicDirectory: MusicDirectory) { + val entries = musicDirectory.getChildren() + + if (model.currentDirectoryIsSortable && Util.getShouldSortByDisc(context)) { + Collections.sort(entries, EntryByDiscAndTrackComparator()) + } + + var allVideos = true + var songCount = 0 + + for (entry in entries) { + if (!entry.isVideo) { + allVideos = false + } + if (!entry.isDirectory) { + songCount++ + } + } + + val listSize = requireArguments().getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0) + + if (songCount > 0) { + if (model.showHeader) { + val intentAlbumName = requireArguments().getString(Constants.INTENT_EXTRA_NAME_NAME) + val directoryName = musicDirectory.name + val header = createHeader( + entries, intentAlbumName ?: directoryName, + songCount + ) + if (header != null && albumListView!!.headerViewsCount == 0) { + albumListView!!.addHeaderView(header, null, false) + } + } + + pinButton!!.visibility = View.VISIBLE + unpinButton!!.visibility = View.VISIBLE + downloadButton!!.visibility = View.VISIBLE + deleteButton!!.visibility = View.VISIBLE + selectButton!!.visibility = if (allVideos) View.GONE else View.VISIBLE + playNowButton!!.visibility = View.VISIBLE + playNextButton!!.visibility = View.VISIBLE + playLastButton!!.visibility = View.VISIBLE + + if (listSize == 0 || songCount < listSize) { + moreButton!!.visibility = View.GONE + } else { + moreButton!!.visibility = View.VISIBLE + if (requireArguments().getInt(Constants.INTENT_EXTRA_NAME_RANDOM, 0) > 0) { + moreButton!!.setOnClickListener { + val offset = requireArguments().getInt( + Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0 + ) + listSize + val bundle = Bundle() + bundle.putInt(Constants.INTENT_EXTRA_NAME_RANDOM, 1) + bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, listSize) + bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, offset) + Navigation.findNavController(requireView()).navigate( + R.id.selectAlbumFragment, bundle + ) } } - - pinButton!!.visibility = View.GONE - unpinButton!!.visibility = View.GONE - downloadButton!!.visibility = View.GONE - deleteButton!!.visibility = View.GONE - selectButton!!.visibility = View.GONE - playNowButton!!.visibility = View.GONE - playNextButton!!.visibility = View.GONE - playLastButton!!.visibility = View.GONE - - if (listSize == 0 || result.first.getChildren().size < listSize) { - albumButtons!!.visibility = View.GONE - } else { - moreButton!!.visibility = View.VISIBLE + } + } else { + if (model.showSelectFolderHeader) { + if (albumListView!!.headerViewsCount == 0) { + albumListView!!.addHeaderView(selectFolderHeader!!.itemView, null, false) } } - enableButtons() + pinButton!!.visibility = View.GONE + unpinButton!!.visibility = View.GONE + downloadButton!!.visibility = View.GONE + deleteButton!!.visibility = View.GONE + selectButton!!.visibility = View.GONE + playNowButton!!.visibility = View.GONE + playNextButton!!.visibility = View.GONE + playLastButton!!.visibility = View.GONE - val isAlbumList = arguments!!.containsKey(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE) - playAllButtonVisible = !(isAlbumList || entries.isEmpty()) && !allVideos - shareButtonVisible = !isOffline(context) && songCount > 0 - - albumListView!!.removeHeaderView(emptyView!!) - if (entries.isEmpty()) { - emptyView!!.text = "No Media Found" - emptyView!!.setPadding(10, 10, 10, 10) - albumListView!!.addHeaderView(emptyView, null, false) - } - - if (playAllButton != null) { - playAllButton!!.isVisible = playAllButtonVisible - } - - if (shareButton != null) { - shareButton!!.isVisible = shareButtonVisible - } - - albumListView!!.adapter = EntryAdapter( - context, - imageLoaderProvider.getImageLoader(), entries, true - ) - - val playAll = arguments!!.getBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false) - if (playAll && songCount > 0) { - playAll( - arguments!!.getBoolean(Constants.INTENT_EXTRA_NAME_SHUFFLE, false), - false - ) + if (listSize == 0 || musicDirectory.getChildren().size < listSize) { + albumButtons!!.visibility = View.GONE + } else { + moreButton!!.visibility = View.VISIBLE } } - protected fun createHeader( - entries: List, - name: CharSequence?, - songCount: Int - ): View? { - val coverArtView = header!!.findViewById(R.id.select_album_art) as ImageView - val artworkSelection = random.nextInt(entries.size) - imageLoaderProvider.getImageLoader().loadImage( - coverArtView, entries[artworkSelection], false, - Util.getAlbumImageSize(context), false, true - ) + enableButtons() - val albumHeader = AlbumHeader.processEntries(context, entries) + val isAlbumList = requireArguments().containsKey( + Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE + ) - val titleView = header!!.findViewById(R.id.select_album_title) as TextView - titleView.text = name ?: getTitle(this@SelectAlbumFragment) // getActionBarSubtitle()); + playAllButtonVisible = !(isAlbumList || entries.isEmpty()) && !allVideos + shareButtonVisible = !isOffline(context) && songCount > 0 - // Don't show a header if all entries are videos - if (albumHeader.isAllVideo) { - return null - } - - val artistView = header!!.findViewById(R.id.select_album_artist) - val artist: String - - artist = if (albumHeader.artists.size == 1) - albumHeader.artists.iterator().next() - else if (albumHeader.grandParents.size == 1) - albumHeader.grandParents.iterator().next() - else - resources.getString(R.string.common_various_artists) - - artistView.text = artist - - val genreView = header!!.findViewById(R.id.select_album_genre) - val genre: String - - genre = if (albumHeader.genres.size == 1) - albumHeader.genres.iterator().next() - else - resources.getString(R.string.common_multiple_genres) - - genreView.text = genre - - val yearView = header!!.findViewById(R.id.select_album_year) - val year: String - - year = if (albumHeader.years.size == 1) - albumHeader.years.iterator().next().toString() - else - resources.getString(R.string.common_multiple_years) - - yearView.text = year - - val songCountView = header!!.findViewById(R.id.select_album_song_count) - val songs = resources.getQuantityString( - R.plurals.select_album_n_songs, songCount, - songCount - ) - songCountView.text = songs - - val duration = Util.formatTotalDuration(albumHeader.totalDuration) - - val durationView = header!!.findViewById(R.id.select_album_duration) - durationView.text = duration - - return header + albumListView!!.removeHeaderView(emptyView!!) + if (entries.isEmpty()) { + emptyView!!.text = getString(R.string.select_album_empty) + emptyView!!.setPadding(10, 10, 10, 10) + albumListView!!.addHeaderView(emptyView, null, false) } + + if (playAllButton != null) { + playAllButton!!.isVisible = playAllButtonVisible + } + + if (shareButton != null) { + shareButton!!.isVisible = shareButtonVisible + } + + albumListView!!.adapter = EntryAdapter( + context, + imageLoaderProvider.getImageLoader(), entries, true + ) + + val playAll = requireArguments().getBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false) + if (playAll && songCount > 0) { + playAll( + requireArguments().getBoolean(Constants.INTENT_EXTRA_NAME_SHUFFLE, false), + false + ) + } + + model.currentDirectoryIsSortable = true + } + + private fun createHeader( + entries: List, + name: CharSequence?, + songCount: Int + ): View? { + val coverArtView = header!!.findViewById(R.id.select_album_art) as ImageView + val artworkSelection = random.nextInt(entries.size) + imageLoaderProvider.getImageLoader().loadImage( + coverArtView, entries[artworkSelection], false, + Util.getAlbumImageSize(context), false, true + ) + + val albumHeader = AlbumHeader.processEntries(context, entries) + + val titleView = header!!.findViewById(R.id.select_album_title) as TextView + titleView.text = name ?: getTitle(this@SelectAlbumFragment) // getActionBarSubtitle()); + + // Don't show a header if all entries are videos + if (albumHeader.isAllVideo) { + return null + } + + val artistView = header!!.findViewById(R.id.select_album_artist) + + val artist: String = when { + albumHeader.artists.size == 1 -> albumHeader.artists.iterator().next() + albumHeader.grandParents.size == 1 -> albumHeader.grandParents.iterator().next() + else -> resources.getString(R.string.common_various_artists) + } + + artistView.text = artist + + val genreView = header!!.findViewById(R.id.select_album_genre) + + val genre: String = if (albumHeader.genres.size == 1) + albumHeader.genres.iterator().next() + else + resources.getString(R.string.common_multiple_genres) + + genreView.text = genre + + val yearView = header!!.findViewById(R.id.select_album_year) + + val year: String = if (albumHeader.years.size == 1) + albumHeader.years.iterator().next().toString() + else + resources.getString(R.string.common_multiple_years) + + yearView.text = year + + val songCountView = header!!.findViewById(R.id.select_album_song_count) + val songs = resources.getQuantityString( + R.plurals.select_album_n_songs, songCount, + songCount + ) + songCountView.text = songs + + val duration = Util.formatTotalDuration(albumHeader.totalDuration) + + val durationView = header!!.findViewById(R.id.select_album_duration) + durationView.text = duration + + return header } private fun getSelectedSongs(albumListView: ListView?): List { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SelectAlbumModel.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SelectAlbumModel.kt new file mode 100644 index 00000000..1cbdc1c6 --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SelectAlbumModel.kt @@ -0,0 +1,351 @@ +package org.moire.ultrasonic.fragment + +import android.app.Application +import android.content.Context +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.MutableLiveData +import java.util.LinkedList +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.koin.core.component.KoinApiExtension +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import org.moire.ultrasonic.R +import org.moire.ultrasonic.api.subsonic.models.AlbumListType +import org.moire.ultrasonic.data.ActiveServerProvider +import org.moire.ultrasonic.domain.MusicDirectory +import org.moire.ultrasonic.domain.MusicFolder +import org.moire.ultrasonic.service.MusicServiceFactory +import org.moire.ultrasonic.util.Util + +// TODO: Break up this class into smaller more specific classes, extending a base class if necessary +@KoinApiExtension +class SelectAlbumModel(application: Application) : AndroidViewModel(application), KoinComponent { + + private val context: Context + get() = getApplication().applicationContext + + private val activeServerProvider: ActiveServerProvider by inject() + + private val allSongsId = "-1" + + val musicFolders: MutableLiveData> = MutableLiveData() + val albumList: MutableLiveData = MutableLiveData() + val currentDirectory: MutableLiveData = MutableLiveData() + val songsForGenre: MutableLiveData = MutableLiveData() + + var currentDirectoryIsSortable = true + var showHeader = true + var showSelectFolderHeader = false + + suspend fun getMusicFolders(refresh: Boolean) { + withContext(Dispatchers.IO) { + if (!ActiveServerProvider.isOffline(context)) { + val musicService = MusicServiceFactory.getMusicService(context) + musicFolders.postValue(musicService.getMusicFolders(refresh, context)) + } + } + } + + suspend fun getMusicDirectory( + refresh: Boolean, + id: String?, + name: String?, + parentId: String? + ) { + withContext(Dispatchers.IO) { + if (!ActiveServerProvider.isOffline(context)) { + val service = MusicServiceFactory.getMusicService(context) + + var root = MusicDirectory() + + if (allSongsId == id) { + val musicDirectory = service.getMusicDirectory( + parentId, name, refresh, context + ) + + val songs: MutableList = LinkedList() + getSongsRecursively(musicDirectory, songs) + + for (song in songs) { + if (!song.isDirectory) { + root.addChild(song) + } + } + } else { + val musicDirectory = service.getMusicDirectory(id, name, refresh, context) + + if (Util.getShouldShowAllSongsByArtist(context) && + musicDirectory.findChild(allSongsId) == null && + hasOnlyFolders(musicDirectory) + ) { + val allSongs = MusicDirectory.Entry() + + allSongs.isDirectory = true + allSongs.artist = name + allSongs.parent = id + allSongs.id = allSongsId + allSongs.title = String.format( + context.resources.getString(R.string.select_album_all_songs), name + ) + + root.addChild(allSongs) + root.addAll(musicDirectory.getChildren()) + } else { + root = musicDirectory + } + } + + currentDirectory.postValue(root) + } + } + } + + // Given a Music directory "songs" it recursively adds all children to "songs" + private fun getSongsRecursively( + parent: MusicDirectory, + songs: MutableList + ) { + val service = MusicServiceFactory.getMusicService(context) + + for (song in parent.getChildren(includeDirs = false, includeFiles = true)) { + if (!song.isVideo && !song.isDirectory) { + songs.add(song) + } + } + + for ((id1, _, _, title) in parent.getChildren(true, includeFiles = false)) { + var root: MusicDirectory + + if (allSongsId != id1) { + root = service.getMusicDirectory(id1, title, false, context) + + getSongsRecursively(root, songs) + } + } + } + + suspend fun getArtist(refresh: Boolean, id: String?, name: String?) { + + withContext(Dispatchers.IO) { + if (!ActiveServerProvider.isOffline(context)) { + val service = MusicServiceFactory.getMusicService(context) + + var root = MusicDirectory() + + val musicDirectory = service.getArtist(id, name, refresh, context) + + if (Util.getShouldShowAllSongsByArtist(context) && + musicDirectory.findChild(allSongsId) == null && + hasOnlyFolders(musicDirectory) + ) { + val allSongs = MusicDirectory.Entry() + + allSongs.isDirectory = true + allSongs.artist = name + allSongs.parent = id + allSongs.id = allSongsId + allSongs.title = String.format( + context.resources.getString(R.string.select_album_all_songs), name + ) + + root.addFirst(allSongs) + root.addAll(musicDirectory.getChildren()) + } else { + root = musicDirectory + } + currentDirectory.postValue(root) + } + } + } + + suspend fun getAlbum(refresh: Boolean, id: String?, name: String?, parentId: String?) { + + withContext(Dispatchers.IO) { + if (!ActiveServerProvider.isOffline(context)) { + + val service = MusicServiceFactory.getMusicService(context) + + val musicDirectory: MusicDirectory + + musicDirectory = if (allSongsId == id) { + val root = MusicDirectory() + + val songs: MutableCollection = LinkedList() + val artist = service.getArtist(parentId, "", false, context) + + for ((id1) in artist.getChildren()) { + if (allSongsId != id1) { + val albumDirectory = service.getAlbum( + id1, "", false, context + ) + + for (song in albumDirectory.getChildren()) { + if (!song.isVideo) { + songs.add(song) + } + } + } + } + + for (song in songs) { + if (!song.isDirectory) { + root.addChild(song) + } + } + root + } else { + service.getAlbum(id, name, refresh, context) + } + currentDirectory.postValue(musicDirectory) + } + } + } + + suspend fun getSongsForGenre(genre: String, count: Int, offset: Int) { + withContext(Dispatchers.IO) { + if (!ActiveServerProvider.isOffline(context)) { + val service = MusicServiceFactory.getMusicService(context) + val musicDirectory = service.getSongsByGenre(genre, count, offset, context) + songsForGenre.postValue(musicDirectory) + } + } + } + + suspend fun getStarred() { + + withContext(Dispatchers.IO) { + if (!ActiveServerProvider.isOffline(context)) { + + val service = MusicServiceFactory.getMusicService(context) + val musicDirectory: MusicDirectory + val context = context + + if (Util.getShouldUseId3Tags(context)) { + musicDirectory = Util.getSongsFromSearchResult(service.getStarred2(context)) + } else { + musicDirectory = Util.getSongsFromSearchResult(service.getStarred(context)) + } + + currentDirectory.postValue(musicDirectory) + } + } + } + + suspend fun getVideos(refresh: Boolean) { + showHeader = false + + withContext(Dispatchers.IO) { + if (!ActiveServerProvider.isOffline(context)) { + val service = MusicServiceFactory.getMusicService(context) + currentDirectory.postValue(service.getVideos(refresh, context)) + } + } + } + + suspend fun getRandom(size: Int) { + + withContext(Dispatchers.IO) { + if (!ActiveServerProvider.isOffline(context)) { + val service = MusicServiceFactory.getMusicService(context) + val musicDirectory = service.getRandomSongs(size, context) + + currentDirectoryIsSortable = false + currentDirectory.postValue(musicDirectory) + } + } + } + + suspend fun getPlaylist(playlistId: String, playlistName: String?) { + + withContext(Dispatchers.IO) { + if (!ActiveServerProvider.isOffline(context)) { + val service = MusicServiceFactory.getMusicService(context) + val musicDirectory = service.getPlaylist(playlistId, playlistName, context) + + currentDirectory.postValue(musicDirectory) + } + } + } + + suspend fun getPodcastEpisodes(podcastChannelId: String) { + + withContext(Dispatchers.IO) { + if (!ActiveServerProvider.isOffline(context)) { + val service = MusicServiceFactory.getMusicService(context) + val musicDirectory = service.getPodcastEpisodes(podcastChannelId, context) + currentDirectory.postValue(musicDirectory) + } + } + } + + suspend fun getShare(shareId: String) { + + withContext(Dispatchers.IO) { + if (!ActiveServerProvider.isOffline(context)) { + val service = MusicServiceFactory.getMusicService(context) + val musicDirectory = MusicDirectory() + + val shares = service.getShares(true, context) + + for (share in shares) { + if (share.id == shareId) { + for (entry in share.getEntries()) { + musicDirectory.addChild(entry) + } + break + } + } + currentDirectory.postValue(musicDirectory) + } + } + } + + suspend fun getAlbumList(albumListType: String, size: Int, offset: Int) { + + showHeader = false + showSelectFolderHeader = !ActiveServerProvider.isOffline(context) && + !Util.getShouldUseId3Tags(context) && ( + (albumListType == AlbumListType.SORTED_BY_NAME.toString()) || + (albumListType == AlbumListType.SORTED_BY_ARTIST.toString()) + ) + + withContext(Dispatchers.IO) { + if (!ActiveServerProvider.isOffline(context)) { + val service = MusicServiceFactory.getMusicService(context) + val musicDirectory: MusicDirectory + val musicFolderId = if (showSelectFolderHeader) { + activeServerProvider.getActiveServer().musicFolderId + } else { + null + } + + if (Util.getShouldUseId3Tags(context)) { + musicDirectory = service.getAlbumList2( + albumListType, size, + offset, musicFolderId, context + ) + } else { + musicDirectory = service.getAlbumList( + albumListType, size, + offset, musicFolderId, context + ) + } + + currentDirectoryIsSortable = sortableCollection(albumListType) + albumList.postValue(musicDirectory) + } + } + } + + private fun sortableCollection(albumListType: String): Boolean { + return albumListType != "newest" && albumListType != "random" && + albumListType != "highest" && albumListType != "recent" && + albumListType != "frequent" + } + + // Returns true if the directory contains only folders + private fun hasOnlyFolders(musicDirectory: MusicDirectory) = + musicDirectory.getChildren(includeDirs = true, includeFiles = false).size == + musicDirectory.getChildren(includeDirs = true, includeFiles = true).size +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ServerSettingsModel.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ServerSettingsModel.kt index 0e1e9f79..02386b5e 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ServerSettingsModel.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ServerSettingsModel.kt @@ -1,9 +1,9 @@ package org.moire.ultrasonic.fragment -import android.content.Context +import android.app.Application import android.content.SharedPreferences +import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData -import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.preference.PreferenceManager import kotlinx.coroutines.CoroutineScope @@ -22,8 +22,8 @@ import timber.log.Timber class ServerSettingsModel( private val repository: ServerSettingDao, private val activeServerProvider: ActiveServerProvider, - private val context: Context -) : ViewModel() { + application: Application +) : AndroidViewModel(application) { companion object { private const val PREFERENCES_KEY_SERVER_MIGRATED = "serverMigrated" @@ -54,6 +54,7 @@ class ServerSettingsModel( if (rowCount == null || rowCount == 0) { // First time load up the server settings from the Preferences val dbServerList = mutableListOf() + val context = getApplication().applicationContext val settings = PreferenceManager.getDefaultSharedPreferences(context) val serverNum = settings.getInt(PREFERENCES_KEY_ACTIVE_SERVERS, 0)