mirror of
https://github.com/ultrasonic/ultrasonic
synced 2025-02-17 04:00:39 +01:00
Merge pull request #493 from tzugen/generic-views
Introduce new Generic Fragments, ViewModels, and Adapters for the display of API data.
This commit is contained in:
commit
950cb6254f
@ -3,13 +3,13 @@ package org.moire.ultrasonic.domain
|
||||
import java.io.Serializable
|
||||
|
||||
data class Artist(
|
||||
var id: String? = null,
|
||||
var name: String? = null,
|
||||
override var id: String? = null,
|
||||
override var name: String? = null,
|
||||
var index: String? = null,
|
||||
var coverArt: String? = null,
|
||||
var albumCount: Long? = null,
|
||||
var closeness: Int = 0
|
||||
) : Serializable {
|
||||
) : Serializable, GenericEntry() {
|
||||
companion object {
|
||||
private const val serialVersionUID = -5790532593784846982L
|
||||
}
|
||||
|
@ -0,0 +1,7 @@
|
||||
package org.moire.ultrasonic.domain
|
||||
|
||||
abstract class GenericEntry {
|
||||
// TODO Should be non-null!
|
||||
abstract val id: String?
|
||||
open val name: String? = null
|
||||
}
|
@ -36,7 +36,7 @@ class MusicDirectory {
|
||||
}
|
||||
|
||||
data class Entry(
|
||||
var id: String? = null,
|
||||
override var id: String? = null,
|
||||
var parent: String? = null,
|
||||
var isDirectory: Boolean = false,
|
||||
var title: String? = null,
|
||||
@ -66,7 +66,7 @@ class MusicDirectory {
|
||||
var bookmarkPosition: Int = 0,
|
||||
var userRating: Int? = null,
|
||||
var averageRating: Float? = null
|
||||
) : Serializable {
|
||||
) : Serializable, GenericEntry() {
|
||||
fun setDuration(duration: Long) {
|
||||
this.duration = duration.toInt()
|
||||
}
|
||||
|
@ -4,6 +4,6 @@ package org.moire.ultrasonic.domain
|
||||
* Represents a top level directory in which music or other media is stored.
|
||||
*/
|
||||
data class MusicFolder(
|
||||
val id: String,
|
||||
val name: String
|
||||
)
|
||||
override val id: String,
|
||||
override val name: String
|
||||
) : GenericEntry()
|
||||
|
@ -3,14 +3,14 @@ package org.moire.ultrasonic.domain
|
||||
import java.io.Serializable
|
||||
|
||||
data class Playlist @JvmOverloads constructor(
|
||||
val id: String,
|
||||
var name: String,
|
||||
override val id: String,
|
||||
override var name: String,
|
||||
val owner: String = "",
|
||||
val comment: String = "",
|
||||
val songCount: String = "",
|
||||
val created: String = "",
|
||||
val public: Boolean? = null
|
||||
) : Serializable {
|
||||
) : Serializable, GenericEntry() {
|
||||
companion object {
|
||||
private const val serialVersionUID = -4160515427075433798L
|
||||
}
|
||||
|
@ -3,12 +3,12 @@ package org.moire.ultrasonic.domain
|
||||
import java.io.Serializable
|
||||
|
||||
data class PodcastsChannel(
|
||||
val id: String,
|
||||
override val id: String,
|
||||
val title: String?,
|
||||
val url: String?,
|
||||
val description: String?,
|
||||
val status: String?
|
||||
) : Serializable {
|
||||
) : Serializable, GenericEntry() {
|
||||
companion object {
|
||||
private const val serialVersionUID = -4160515427075433798L
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import java.io.Serializable
|
||||
import org.moire.ultrasonic.domain.MusicDirectory.Entry
|
||||
|
||||
data class Share(
|
||||
var id: String? = null,
|
||||
override var id: String? = null,
|
||||
var url: String? = null,
|
||||
var description: String? = null,
|
||||
var username: String? = null,
|
||||
@ -13,8 +13,8 @@ data class Share(
|
||||
var expires: String? = null,
|
||||
var visitCount: Long? = null,
|
||||
private val entries: MutableList<Entry> = mutableListOf()
|
||||
) : Serializable {
|
||||
val name: String?
|
||||
) : Serializable, GenericEntry() {
|
||||
override val name: String?
|
||||
get() = url?.let { urlPattern.matcher(url).replaceFirst("$1") }
|
||||
|
||||
fun getEntries(): List<Entry> {
|
||||
|
@ -24,25 +24,11 @@
|
||||
<ID>ComplexCondition:FilePickerAdapter.kt$FilePickerAdapter$currentDirectory.absolutePath == "/" || currentDirectory.absolutePath == "/storage" || currentDirectory.absolutePath == "/storage/emulated" || currentDirectory.absolutePath == "/mnt"</ID>
|
||||
<ID>ComplexCondition:LocalMediaPlayer.kt$LocalMediaPlayer$Util.getGaplessPlaybackPreference() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && ( playerState === PlayerState.STARTED || playerState === PlayerState.PAUSED )</ID>
|
||||
<ID>ComplexCondition:SongView.kt$SongView$TextUtils.isEmpty(transcodedSuffix) || transcodedSuffix == suffix || song.isVideo && Util.getVideoPlayerType() !== VideoPlayerType.FLASH</ID>
|
||||
<ID>ComplexMethod:CommunicationErrorHandler.kt$CommunicationErrorHandler.Companion$fun getErrorMessage(error: Throwable, context: Context): String</ID>
|
||||
<ID>ComplexMethod:DownloadFile.kt$DownloadFile.DownloadTask$override fun execute()</ID>
|
||||
<ID>ComplexMethod:EditServerFragment.kt$EditServerFragment$ private fun areFieldsChanged(): Boolean</ID>
|
||||
<ID>ComplexMethod:EditServerFragment.kt$EditServerFragment$ private fun getFields(): Boolean</ID>
|
||||
<ID>ComplexMethod:EditServerFragment.kt$EditServerFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?)</ID>
|
||||
<ID>ComplexMethod:FilePickerAdapter.kt$FilePickerAdapter$private fun fileLister(currentDirectory: File)</ID>
|
||||
<ID>ComplexMethod:LocalMediaPlayer.kt$LocalMediaPlayer$@Synchronized private fun doPlay(downloadFile: DownloadFile, position: Int, start: Boolean)</ID>
|
||||
<ID>ComplexMethod:LocalMediaPlayer.kt$LocalMediaPlayer$@Synchronized private fun setupNext(downloadFile: DownloadFile)</ID>
|
||||
<ID>ComplexMethod:MediaPlayerService.kt$MediaPlayerService$private fun setupOnSongCompletedHandler()</ID>
|
||||
<ID>ComplexMethod:RestErrorMapper.kt$ fun SubsonicRESTException.getLocalizedErrorMessage(context: Context): String</ID>
|
||||
<ID>ComplexMethod:SelectAlbumFragment.kt$SelectAlbumFragment$override fun onContextItemSelected(menuItem: MenuItem): Boolean</ID>
|
||||
<ID>ComplexMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun enableButtons()</ID>
|
||||
<ID>ComplexMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun updateDisplay(refresh: Boolean)</ID>
|
||||
<ID>ComplexMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun updateInterfaceWithEntries(musicDirectory: MusicDirectory)</ID>
|
||||
<ID>ComplexMethod:SelectArtistFragment.kt$SelectArtistFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?)</ID>
|
||||
<ID>ComplexMethod:ServerRowAdapter.kt$ServerRowAdapter$ override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View?</ID>
|
||||
<ID>ComplexMethod:SongView.kt$SongView$fun setSong(song: MusicDirectory.Entry, checkable: Boolean, draggable: Boolean)</ID>
|
||||
<ID>ComplexMethod:SongView.kt$SongView$private fun updateDownloadStatus(downloadFile: DownloadFile)</ID>
|
||||
<ID>ComplexMethod:SongView.kt$SongView$public override fun update()</ID>
|
||||
<ID>ComplexMethod:TrackCollectionFragment.kt$TrackCollectionFragment$private fun enableButtons()</ID>
|
||||
<ID>ComplexMethod:TrackCollectionFragment.kt$TrackCollectionFragment$private fun updateInterfaceWithEntries(musicDirectory: MusicDirectory)</ID>
|
||||
<ID>EmptyCatchBlock:LocalMediaPlayer.kt$LocalMediaPlayer${ }</ID>
|
||||
<ID>EmptyDefaultConstructor:VideoPlayer.kt$VideoPlayer$()</ID>
|
||||
<ID>EmptyFunctionBlock:SongView.kt$SongView${}</ID>
|
||||
@ -61,94 +47,23 @@
|
||||
<ID>ImplicitDefaultLocale:SongView.kt$SongView$String.format("%02d.", trackNumber)</ID>
|
||||
<ID>ImplicitDefaultLocale:SongView.kt$SongView$String.format("%s ", bitRate)</ID>
|
||||
<ID>ImplicitDefaultLocale:SongView.kt$SongView$String.format("%s > %s", suffix, transcodedSuffix)</ID>
|
||||
<ID>LargeClass:DownloadFile.kt$DownloadFile</ID>
|
||||
<ID>LargeClass:DownloadHandler.kt$DownloadHandler</ID>
|
||||
<ID>LargeClass:EditServerFragment.kt$EditServerFragment : FragmentOnBackPressedHandler</ID>
|
||||
<ID>LargeClass:FilePickerAdapter.kt$FilePickerAdapter : Adapter</ID>
|
||||
<ID>LargeClass:LocalMediaPlayer.kt$LocalMediaPlayer</ID>
|
||||
<ID>LargeClass:MediaPlayerService.kt$MediaPlayerService : Service</ID>
|
||||
<ID>LargeClass:NavigationActivity.kt$NavigationActivity : AppCompatActivity</ID>
|
||||
<ID>LargeClass:RESTMusicService.kt$RESTMusicService : MusicService</ID>
|
||||
<ID>LargeClass:SelectAlbumFragment.kt$SelectAlbumFragment : Fragment</ID>
|
||||
<ID>LargeClass:SelectAlbumModel.kt$SelectAlbumModel : AndroidViewModelKoinComponent</ID>
|
||||
<ID>LargeClass:SelectArtistFragment.kt$SelectArtistFragment : Fragment</ID>
|
||||
<ID>LargeClass:ServerSettingsModel.kt$ServerSettingsModel : AndroidViewModel</ID>
|
||||
<ID>LargeClass:SongView.kt$SongView : UpdateViewCheckable</ID>
|
||||
<ID>LongMethod:APIMusicDirectoryConverter.kt$fun MusicDirectoryChild.toDomainEntity(): MusicDirectory.Entry</ID>
|
||||
<ID>LongMethod:ActiveServerProvider.kt$ActiveServerProvider$ fun getActiveServer(): ServerSetting</ID>
|
||||
<ID>LongMethod:ArtistListModel.kt$ArtistListModel$private suspend fun loadFromServer(refresh: Boolean, swipe: SwipeRefreshLayout)</ID>
|
||||
<ID>LongMethod:ArtistRowAdapter.kt$ArtistRowAdapter$override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int)</ID>
|
||||
<ID>LongMethod:CommunicationErrorHandler.kt$CommunicationErrorHandler.Companion$fun getErrorMessage(error: Throwable, context: Context): String</ID>
|
||||
<ID>LongMethod:DownloadFile.kt$DownloadFile$private fun updateModificationDate(file: File)</ID>
|
||||
<ID>LargeClass:TrackCollectionFragment.kt$TrackCollectionFragment : Fragment</ID>
|
||||
<ID>LongMethod:ArtistListFragment.kt$ArtistListFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?)</ID>
|
||||
<ID>LongMethod:ArtistListFragment.kt$ArtistListFragment$private fun onArtistMenuItemSelected(menuItem: MenuItem, artist: Artist): Boolean</ID>
|
||||
<ID>LongMethod:DownloadFile.kt$DownloadFile.DownloadTask$override fun execute()</ID>
|
||||
<ID>LongMethod:DownloadHandler.kt$DownloadHandler$fun download( fragment: Fragment, append: Boolean, save: Boolean, autoPlay: Boolean, playNext: Boolean, shuffle: Boolean, songs: List<MusicDirectory.Entry?> )</ID>
|
||||
<ID>LongMethod:DownloadHandler.kt$DownloadHandler.<no name provided>$@Throws(Exception::class) private fun getSongsRecursively( parent: MusicDirectory, songs: MutableList<MusicDirectory.Entry> )</ID>
|
||||
<ID>LongMethod:DownloadHandler.kt$DownloadHandler.<no name provided>$@Throws(Throwable::class) override fun doInBackground(): List<MusicDirectory.Entry></ID>
|
||||
<ID>LongMethod:DownloadHandler.kt$DownloadHandler.<no name provided>$override fun done(songs: List<MusicDirectory.Entry>)</ID>
|
||||
<ID>LongMethod:EditServerFragment.kt$EditServerFragment$ private fun finishActivity()</ID>
|
||||
<ID>LongMethod:EditServerFragment.kt$EditServerFragment$ private fun getFields(): Boolean</ID>
|
||||
<ID>LongMethod:EditServerFragment.kt$EditServerFragment$override fun onSaveInstanceState(savedInstanceState: Bundle)</ID>
|
||||
<ID>LongMethod:EditServerFragment.kt$EditServerFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?)</ID>
|
||||
<ID>LongMethod:EditServerFragment.kt$EditServerFragment$override fun onViewStateRestored(savedInstanceState: Bundle?)</ID>
|
||||
<ID>LongMethod:EditServerFragment.kt$EditServerFragment.<no name provided>$@Throws(Throwable::class) override fun doInBackground(): Boolean</ID>
|
||||
<ID>LongMethod:FileLoggerTree.kt$FileLoggerTree$ override fun log(priority: Int, tag: String?, message: String, t: Throwable?)</ID>
|
||||
<ID>LongMethod:FileLoggerTree.kt$FileLoggerTree$ private fun getNextLogFile()</ID>
|
||||
<ID>LongMethod:FilePickerAdapter.kt$FilePickerAdapter$fun createNewFolder()</ID>
|
||||
<ID>LongMethod:FilePickerAdapter.kt$FilePickerAdapter$private fun fileLister(currentDirectory: File)</ID>
|
||||
<ID>LongMethod:FilePickerAdapter.kt$FilePickerAdapter$private fun getKitKatStorageItems(storages: List<File>): LinkedList<FileListItem></ID>
|
||||
<ID>LongMethod:FilePickerAdapter.kt$FilePickerAdapter$private fun getStorageItems(): LinkedList<FileListItem></ID>
|
||||
<ID>LongMethod:FilePickerDialog.kt$FilePickerDialog$private fun initialize(context: Context)</ID>
|
||||
<ID>LongMethod:ImageLoaderProvider.kt$ImageLoaderProvider$@Synchronized fun getImageLoader(): ImageLoader</ID>
|
||||
<ID>LongMethod:LocalMediaPlayer.kt$LocalMediaPlayer$@Synchronized fun setPlayerState(playerState: PlayerState)</ID>
|
||||
<ID>LongMethod:LocalMediaPlayer.kt$LocalMediaPlayer$@Synchronized private fun doPlay(downloadFile: DownloadFile, position: Int, start: Boolean)</ID>
|
||||
<ID>LongMethod:LocalMediaPlayer.kt$LocalMediaPlayer$@Synchronized private fun setupNext(downloadFile: DownloadFile)</ID>
|
||||
<ID>LongMethod:LocalMediaPlayer.kt$LocalMediaPlayer$fun init()</ID>
|
||||
<ID>LongMethod:LocalMediaPlayer.kt$LocalMediaPlayer$fun release()</ID>
|
||||
<ID>LongMethod:LocalMediaPlayer.kt$LocalMediaPlayer.<no name provided>$override fun onCompletion(mediaPlayer: MediaPlayer)</ID>
|
||||
<ID>LongMethod:MediaPlayerService.kt$MediaPlayerService$ private fun buildForegroundNotification( playerState: PlayerState, currentPlaying: DownloadFile? ): Notification</ID>
|
||||
<ID>LongMethod:MediaPlayerService.kt$MediaPlayerService$@Synchronized fun setNextPlaying()</ID>
|
||||
<ID>LongMethod:MediaPlayerService.kt$MediaPlayerService$override fun onCreate()</ID>
|
||||
<ID>LongMethod:MediaPlayerService.kt$MediaPlayerService$private fun addActions( context: Context, notificationBuilder: NotificationCompat.Builder, playerState: PlayerState, song: MusicDirectory.Entry? ): IntArray</ID>
|
||||
<ID>LongMethod:MediaPlayerService.kt$MediaPlayerService$private fun generateAction(context: Context, requestCode: Int): NotificationCompat.Action?</ID>
|
||||
<ID>LongMethod:MediaPlayerService.kt$MediaPlayerService$private fun setupOnCurrentPlayingChangedHandler()</ID>
|
||||
<ID>LongMethod:MediaPlayerService.kt$MediaPlayerService$private fun setupOnPlayerStateChangedHandler()</ID>
|
||||
<ID>LongMethod:MediaPlayerService.kt$MediaPlayerService$private fun setupOnSongCompletedHandler()</ID>
|
||||
<ID>LongMethod:MediaPlayerService.kt$MediaPlayerService$private fun updateMediaSession(currentPlaying: DownloadFile?, playerState: PlayerState)</ID>
|
||||
<ID>LongMethod:NavigationActivity.kt$NavigationActivity$// TODO Test if this works with external Intents // android.intent.action.SEARCH and android.media.action.MEDIA_PLAY_FROM_SEARCH calls here override fun onNewIntent(intent: Intent?)</ID>
|
||||
<ID>LongMethod:NavigationActivity.kt$NavigationActivity$override fun onCreate(savedInstanceState: Bundle?)</ID>
|
||||
<ID>LongMethod:NavigationActivity.kt$NavigationActivity$private fun showNowPlaying()</ID>
|
||||
<ID>LongMethod:RESTMusicService.kt$RESTMusicService$@Throws(Exception::class) override fun getAvatar( username: String?, size: Int, saveToFile: Boolean, highQuality: Boolean ): Bitmap?</ID>
|
||||
<ID>LongMethod:RESTMusicService.kt$RESTMusicService$@Throws(Exception::class) override fun getCoverArt( entry: MusicDirectory.Entry?, size: Int, saveToFile: Boolean, highQuality: Boolean ): Bitmap?</ID>
|
||||
<ID>LongMethod:RESTMusicService.kt$RESTMusicService$@Throws(IOException::class) private fun savePlaylist( name: String?, playlist: MusicDirectory )</ID>
|
||||
<ID>LongMethod:RestErrorMapper.kt$ fun SubsonicRESTException.getLocalizedErrorMessage(context: Context): String</ID>
|
||||
<ID>LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$override fun onContextItemSelected(menuItem: MenuItem): Boolean</ID>
|
||||
<ID>LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?)</ID>
|
||||
<ID>LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun createHeader( entries: List<MusicDirectory.Entry>, name: CharSequence?, songCount: Int ): View?</ID>
|
||||
<ID>LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun downloadBackground(save: Boolean, songs: List<MusicDirectory.Entry?>)</ID>
|
||||
<ID>LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun enableButtons()</ID>
|
||||
<ID>LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun playAll(shuffle: Boolean = false, append: Boolean = false)</ID>
|
||||
<ID>LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun updateDisplay(refresh: Boolean)</ID>
|
||||
<ID>LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun updateInterfaceWithEntries(musicDirectory: MusicDirectory)</ID>
|
||||
<ID>LongMethod:SelectAlbumModel.kt$SelectAlbumModel$suspend fun getAlbum(refresh: Boolean, id: String?, name: String?, parentId: String?)</ID>
|
||||
<ID>LongMethod:SelectAlbumModel.kt$SelectAlbumModel$suspend fun getAlbumList(albumListType: String, size: Int, offset: Int)</ID>
|
||||
<ID>LongMethod:SelectAlbumModel.kt$SelectAlbumModel$suspend fun getArtist(refresh: Boolean, id: String?, name: String?)</ID>
|
||||
<ID>LongMethod:SelectAlbumModel.kt$SelectAlbumModel$suspend fun getMusicDirectory( refresh: Boolean, id: String?, name: String?, parentId: String? )</ID>
|
||||
<ID>LongMethod:SelectArtistFragment.kt$SelectArtistFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?)</ID>
|
||||
<ID>LongMethod:SelectArtistFragment.kt$SelectArtistFragment$private fun onArtistMenuItemSelected(menuItem: MenuItem, artist: Artist): Boolean</ID>
|
||||
<ID>LongMethod:ServerRowAdapter.kt$ServerRowAdapter$ override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View?</ID>
|
||||
<ID>LongMethod:ServerRowAdapter.kt$ServerRowAdapter$ private fun popupMenuItemClick(menuItem: MenuItem, position: Int): Boolean</ID>
|
||||
<ID>LongMethod:ServerRowAdapter.kt$ServerRowAdapter$ private fun serverMenuClick(view: View, position: Int)</ID>
|
||||
<ID>LongMethod:ServerSelectorFragment.kt$ServerSelectorFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?)</ID>
|
||||
<ID>LongMethod:ServerSettingsModel.kt$ServerSettingsModel$ fun migrateFromPreferences(): Boolean</ID>
|
||||
<ID>LongMethod:ServerSettingsModel.kt$ServerSettingsModel$ private fun loadServerSettingFromPreferences( preferenceId: Int, serverId: Int, settings: SharedPreferences ): ServerSetting?</ID>
|
||||
<ID>LongMethod:ShareHandler.kt$ShareHandler$private fun showDialog( fragment: Fragment, shareDetails: ShareDetails, swipe: SwipeRefreshLayout?, cancellationToken: CancellationToken )</ID>
|
||||
<ID>LongMethod:SongView.kt$SongView$fun setLayout(song: MusicDirectory.Entry)</ID>
|
||||
<ID>LongMethod:SongView.kt$SongView$fun setSong(song: MusicDirectory.Entry, checkable: Boolean, draggable: Boolean)</ID>
|
||||
<ID>LongMethod:SongView.kt$SongView$private fun updateDownloadStatus(downloadFile: DownloadFile)</ID>
|
||||
<ID>LongMethod:SongView.kt$SongView$public override fun update()</ID>
|
||||
<ID>LongMethod:SubsonicUncaughtExceptionHandler.kt$SubsonicUncaughtExceptionHandler$override fun uncaughtException(thread: Thread, throwable: Throwable)</ID>
|
||||
<ID>LongMethod:UApp.kt$UApp$override fun onCreate()</ID>
|
||||
<ID>LongParameterList:ArtistRowAdapter.kt$ArtistRowAdapter$( private var artistList: List<Artist>, private var selectFolderHeader: SelectMusicFolderView?, val onArtistClick: (Artist) -> Unit, val onContextMenuClick: (MenuItem, Artist) -> Boolean, private val imageLoader: ImageLoader )</ID>
|
||||
<ID>LongMethod:TrackCollectionFragment.kt$TrackCollectionFragment$override fun onContextItemSelected(menuItem: MenuItem): Boolean</ID>
|
||||
<ID>LongMethod:TrackCollectionFragment.kt$TrackCollectionFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?)</ID>
|
||||
<ID>LongMethod:TrackCollectionFragment.kt$TrackCollectionFragment$private fun updateDisplay(refresh: Boolean)</ID>
|
||||
<ID>LongMethod:TrackCollectionFragment.kt$TrackCollectionFragment$private fun updateInterfaceWithEntries(musicDirectory: MusicDirectory)</ID>
|
||||
<ID>LongParameterList:DownloadHandler.kt$DownloadHandler$( fragment: Fragment, append: Boolean, save: Boolean, autoPlay: Boolean, playNext: Boolean, shuffle: Boolean, songs: List<MusicDirectory.Entry?> )</ID>
|
||||
<ID>LongParameterList:DownloadHandler.kt$DownloadHandler$( 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 )</ID>
|
||||
<ID>LongParameterList:DownloadHandler.kt$DownloadHandler$( fragment: Fragment, id: String, name: String?, save: Boolean, append: Boolean, autoplay: Boolean, shuffle: Boolean, background: Boolean, playNext: Boolean, unpin: Boolean )</ID>
|
||||
@ -188,11 +103,11 @@
|
||||
<ID>MagicNumber:MediaPlayerService.kt$MediaPlayerService.Companion$50L</ID>
|
||||
<ID>MagicNumber:RESTMusicService.kt$RESTMusicService$206</ID>
|
||||
<ID>MagicNumber:RESTMusicService.kt$RESTMusicService$5</ID>
|
||||
<ID>MagicNumber:SelectAlbumFragment.kt$SelectAlbumFragment$10</ID>
|
||||
<ID>MagicNumber:SelectMusicFolderView.kt$SelectMusicFolderView$10</ID>
|
||||
<ID>MagicNumber:SongView.kt$SongView$3</ID>
|
||||
<ID>MagicNumber:SongView.kt$SongView$4</ID>
|
||||
<ID>MagicNumber:SongView.kt$SongView$60</ID>
|
||||
<ID>MagicNumber:TrackCollectionFragment.kt$TrackCollectionFragment$10</ID>
|
||||
<ID>NestedBlockDepth:DownloadFile.kt$DownloadFile.DownloadTask$override fun execute()</ID>
|
||||
<ID>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 )</ID>
|
||||
<ID>NestedBlockDepth:MediaPlayerService.kt$MediaPlayerService$private fun setupOnSongCompletedHandler()</ID>
|
||||
@ -202,9 +117,9 @@
|
||||
<ID>ReturnCount:MediaPlayerService.kt$MediaPlayerService$private fun generateAction(context: Context, requestCode: Int): NotificationCompat.Action?</ID>
|
||||
<ID>ReturnCount:RESTMusicService.kt$RESTMusicService$@Throws(Exception::class) override fun getAvatar( username: String?, size: Int, saveToFile: Boolean, highQuality: Boolean ): Bitmap?</ID>
|
||||
<ID>ReturnCount:RESTMusicService.kt$RESTMusicService$@Throws(Exception::class) override fun getCoverArt( entry: MusicDirectory.Entry?, size: Int, saveToFile: Boolean, highQuality: Boolean ): Bitmap?</ID>
|
||||
<ID>ReturnCount:SelectAlbumFragment.kt$SelectAlbumFragment$override fun onContextItemSelected(menuItem: MenuItem): Boolean</ID>
|
||||
<ID>ReturnCount:SelectAlbumFragment.kt$SelectAlbumFragment$override fun onOptionsItemSelected(item: MenuItem): Boolean</ID>
|
||||
<ID>ReturnCount:ServerRowAdapter.kt$ServerRowAdapter$ private fun popupMenuItemClick(menuItem: MenuItem, position: Int): Boolean</ID>
|
||||
<ID>ReturnCount:TrackCollectionFragment.kt$TrackCollectionFragment$override fun onContextItemSelected(menuItem: MenuItem): Boolean</ID>
|
||||
<ID>ReturnCount:TrackCollectionFragment.kt$TrackCollectionFragment$override fun onOptionsItemSelected(item: MenuItem): Boolean</ID>
|
||||
<ID>SpreadOperator:MediaPlayerService.kt$MediaPlayerService$(*compactActions)</ID>
|
||||
<ID>SwallowedException:DownloadFile.kt$DownloadFile$catch (e: Exception) { Timber.w("Failed to set last-modified date on %s", file) }</ID>
|
||||
<ID>SwallowedException:DownloadFile.kt$DownloadFile$catch (ex: IOException) { Timber.w("Failed to rename file %s to %s", completeFile, saveFile) }</ID>
|
||||
@ -231,7 +146,7 @@
|
||||
<ID>TooManyFunctions:LocalMediaPlayer.kt$LocalMediaPlayer</ID>
|
||||
<ID>TooManyFunctions:MediaPlayerService.kt$MediaPlayerService : Service</ID>
|
||||
<ID>TooManyFunctions:RESTMusicService.kt$RESTMusicService : MusicService</ID>
|
||||
<ID>TooManyFunctions:SelectAlbumFragment.kt$SelectAlbumFragment : Fragment</ID>
|
||||
<ID>TooManyFunctions:TrackCollectionFragment.kt$TrackCollectionFragment : Fragment</ID>
|
||||
<ID>TopLevelPropertyNaming:SubsonicUncaughtExceptionHandler.kt$private const val filename = "ultrasonic-stacktrace.txt"</ID>
|
||||
<ID>UnusedPrivateMember:RESTMusicService.kt$RESTMusicService.Companion$private const val INDEXES_FOLDER_STORAGE_NAME = "indexes_folder"</ID>
|
||||
<ID>UselessCallOnNotNull:FileLoggerTree.kt$FileLoggerTree$fileList.isNullOrEmpty()</ID>
|
||||
|
@ -2,14 +2,8 @@
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues></ManuallySuppressedIssues>
|
||||
<CurrentIssues>
|
||||
<ID>ComplexMethod:AlbumListType.kt$AlbumListType.Companion$@JvmStatic fun fromName(typeName: String): AlbumListType</ID>
|
||||
<ID>ComplexMethod:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$@JvmStatic @Throws(IllegalArgumentException::class) fun getClosestKnownClientApiVersion(apiVersion: String): SubsonicAPIVersions</ID>
|
||||
<ID>ComplexMethod:SubsonicError.kt$SubsonicError.Companion$fun getError(code: Int, message: String)</ID>
|
||||
<ID>EmptyFunctionBlock:SubsonicAPIClient.kt$SubsonicAPIClient.<no name provided>${}</ID>
|
||||
<ID>LargeClass:ApiVersionCheckWrapper.kt$ApiVersionCheckWrapper : SubsonicAPIDefinition</ID>
|
||||
<ID>LargeClass:SubsonicAPIDefinition.kt$SubsonicAPIDefinition</ID>
|
||||
<ID>LongMethod:SubsonicAPIClient.kt$SubsonicAPIClient$private inline fun handleStreamResponse(apiCall: () -> Response<ResponseBody>): StreamResponse</ID>
|
||||
<ID>LongMethod:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$@JvmStatic @Throws(IllegalArgumentException::class) fun getClosestKnownClientApiVersion(apiVersion: String): SubsonicAPIVersions</ID>
|
||||
<ID>MagicNumber:PasswordExt.kt$0xFF</ID>
|
||||
<ID>MagicNumber:PasswordExt.kt$4</ID>
|
||||
<ID>MagicNumber:PasswordMD5Interceptor.kt$PasswordMD5Interceptor$16</ID>
|
||||
|
@ -24,25 +24,11 @@
|
||||
<ID>ComplexCondition:FilePickerAdapter.kt$FilePickerAdapter$currentDirectory.absolutePath == "/" || currentDirectory.absolutePath == "/storage" || currentDirectory.absolutePath == "/storage/emulated" || currentDirectory.absolutePath == "/mnt"</ID>
|
||||
<ID>ComplexCondition:LocalMediaPlayer.kt$LocalMediaPlayer$Util.getGaplessPlaybackPreference() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && ( playerState === PlayerState.STARTED || playerState === PlayerState.PAUSED )</ID>
|
||||
<ID>ComplexCondition:SongView.kt$SongView$TextUtils.isEmpty(transcodedSuffix) || transcodedSuffix == suffix || song.isVideo && Util.getVideoPlayerType() !== VideoPlayerType.FLASH</ID>
|
||||
<ID>ComplexMethod:CommunicationErrorHandler.kt$CommunicationErrorHandler.Companion$fun getErrorMessage(error: Throwable, context: Context): String</ID>
|
||||
<ID>ComplexMethod:DownloadFile.kt$DownloadFile.DownloadTask$override fun execute()</ID>
|
||||
<ID>ComplexMethod:EditServerFragment.kt$EditServerFragment$ private fun areFieldsChanged(): Boolean</ID>
|
||||
<ID>ComplexMethod:EditServerFragment.kt$EditServerFragment$ private fun getFields(): Boolean</ID>
|
||||
<ID>ComplexMethod:EditServerFragment.kt$EditServerFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?)</ID>
|
||||
<ID>ComplexMethod:FilePickerAdapter.kt$FilePickerAdapter$private fun fileLister(currentDirectory: File)</ID>
|
||||
<ID>ComplexMethod:LocalMediaPlayer.kt$LocalMediaPlayer$@Synchronized private fun doPlay(downloadFile: DownloadFile, position: Int, start: Boolean)</ID>
|
||||
<ID>ComplexMethod:LocalMediaPlayer.kt$LocalMediaPlayer$@Synchronized private fun setupNext(downloadFile: DownloadFile)</ID>
|
||||
<ID>ComplexMethod:MediaPlayerService.kt$MediaPlayerService$private fun setupOnSongCompletedHandler()</ID>
|
||||
<ID>ComplexMethod:RestErrorMapper.kt$ fun SubsonicRESTException.getLocalizedErrorMessage(context: Context): String</ID>
|
||||
<ID>ComplexMethod:SelectAlbumFragment.kt$SelectAlbumFragment$override fun onContextItemSelected(menuItem: MenuItem): Boolean</ID>
|
||||
<ID>ComplexMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun enableButtons()</ID>
|
||||
<ID>ComplexMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun updateDisplay(refresh: Boolean)</ID>
|
||||
<ID>ComplexMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun updateInterfaceWithEntries(musicDirectory: MusicDirectory)</ID>
|
||||
<ID>ComplexMethod:SelectArtistFragment.kt$SelectArtistFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?)</ID>
|
||||
<ID>ComplexMethod:ServerRowAdapter.kt$ServerRowAdapter$ override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View?</ID>
|
||||
<ID>ComplexMethod:SongView.kt$SongView$fun setSong(song: MusicDirectory.Entry, checkable: Boolean, draggable: Boolean)</ID>
|
||||
<ID>ComplexMethod:SongView.kt$SongView$private fun updateDownloadStatus(downloadFile: DownloadFile)</ID>
|
||||
<ID>ComplexMethod:SongView.kt$SongView$public override fun update()</ID>
|
||||
<ID>ComplexMethod:TrackCollectionFragment.kt$TrackCollectionFragment$private fun enableButtons()</ID>
|
||||
<ID>ComplexMethod:TrackCollectionFragment.kt$TrackCollectionFragment$private fun updateInterfaceWithEntries(musicDirectory: MusicDirectory)</ID>
|
||||
<ID>EmptyCatchBlock:LocalMediaPlayer.kt$LocalMediaPlayer${ }</ID>
|
||||
<ID>EmptyDefaultConstructor:VideoPlayer.kt$VideoPlayer$()</ID>
|
||||
<ID>EmptyFunctionBlock:SongView.kt$SongView${}</ID>
|
||||
@ -61,94 +47,23 @@
|
||||
<ID>ImplicitDefaultLocale:SongView.kt$SongView$String.format("%02d.", trackNumber)</ID>
|
||||
<ID>ImplicitDefaultLocale:SongView.kt$SongView$String.format("%s ", bitRate)</ID>
|
||||
<ID>ImplicitDefaultLocale:SongView.kt$SongView$String.format("%s > %s", suffix, transcodedSuffix)</ID>
|
||||
<ID>LargeClass:DownloadFile.kt$DownloadFile</ID>
|
||||
<ID>LargeClass:DownloadHandler.kt$DownloadHandler</ID>
|
||||
<ID>LargeClass:EditServerFragment.kt$EditServerFragment : FragmentOnBackPressedHandler</ID>
|
||||
<ID>LargeClass:FilePickerAdapter.kt$FilePickerAdapter : Adapter</ID>
|
||||
<ID>LargeClass:LocalMediaPlayer.kt$LocalMediaPlayer</ID>
|
||||
<ID>LargeClass:MediaPlayerService.kt$MediaPlayerService : Service</ID>
|
||||
<ID>LargeClass:NavigationActivity.kt$NavigationActivity : AppCompatActivity</ID>
|
||||
<ID>LargeClass:RESTMusicService.kt$RESTMusicService : MusicService</ID>
|
||||
<ID>LargeClass:SelectAlbumFragment.kt$SelectAlbumFragment : Fragment</ID>
|
||||
<ID>LargeClass:SelectAlbumModel.kt$SelectAlbumModel : AndroidViewModelKoinComponent</ID>
|
||||
<ID>LargeClass:SelectArtistFragment.kt$SelectArtistFragment : Fragment</ID>
|
||||
<ID>LargeClass:ServerSettingsModel.kt$ServerSettingsModel : AndroidViewModel</ID>
|
||||
<ID>LargeClass:SongView.kt$SongView : UpdateViewCheckable</ID>
|
||||
<ID>LongMethod:APIMusicDirectoryConverter.kt$fun MusicDirectoryChild.toDomainEntity(): MusicDirectory.Entry</ID>
|
||||
<ID>LongMethod:ActiveServerProvider.kt$ActiveServerProvider$ fun getActiveServer(): ServerSetting</ID>
|
||||
<ID>LongMethod:ArtistListModel.kt$ArtistListModel$private suspend fun loadFromServer(refresh: Boolean, swipe: SwipeRefreshLayout)</ID>
|
||||
<ID>LongMethod:ArtistRowAdapter.kt$ArtistRowAdapter$override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int)</ID>
|
||||
<ID>LongMethod:CommunicationErrorHandler.kt$CommunicationErrorHandler.Companion$fun getErrorMessage(error: Throwable, context: Context): String</ID>
|
||||
<ID>LongMethod:DownloadFile.kt$DownloadFile$private fun updateModificationDate(file: File)</ID>
|
||||
<ID>LargeClass:TrackCollectionFragment.kt$TrackCollectionFragment : Fragment</ID>
|
||||
<ID>LongMethod:ArtistListFragment.kt$ArtistListFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?)</ID>
|
||||
<ID>LongMethod:ArtistListFragment.kt$ArtistListFragment$private fun onArtistMenuItemSelected(menuItem: MenuItem, artist: Artist): Boolean</ID>
|
||||
<ID>LongMethod:DownloadFile.kt$DownloadFile.DownloadTask$override fun execute()</ID>
|
||||
<ID>LongMethod:DownloadHandler.kt$DownloadHandler$fun download( fragment: Fragment, append: Boolean, save: Boolean, autoPlay: Boolean, playNext: Boolean, shuffle: Boolean, songs: List<MusicDirectory.Entry?> )</ID>
|
||||
<ID>LongMethod:DownloadHandler.kt$DownloadHandler.<no name provided>$@Throws(Exception::class) private fun getSongsRecursively( parent: MusicDirectory, songs: MutableList<MusicDirectory.Entry> )</ID>
|
||||
<ID>LongMethod:DownloadHandler.kt$DownloadHandler.<no name provided>$@Throws(Throwable::class) override fun doInBackground(): List<MusicDirectory.Entry></ID>
|
||||
<ID>LongMethod:DownloadHandler.kt$DownloadHandler.<no name provided>$override fun done(songs: List<MusicDirectory.Entry>)</ID>
|
||||
<ID>LongMethod:EditServerFragment.kt$EditServerFragment$ private fun finishActivity()</ID>
|
||||
<ID>LongMethod:EditServerFragment.kt$EditServerFragment$ private fun getFields(): Boolean</ID>
|
||||
<ID>LongMethod:EditServerFragment.kt$EditServerFragment$override fun onSaveInstanceState(savedInstanceState: Bundle)</ID>
|
||||
<ID>LongMethod:EditServerFragment.kt$EditServerFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?)</ID>
|
||||
<ID>LongMethod:EditServerFragment.kt$EditServerFragment$override fun onViewStateRestored(savedInstanceState: Bundle?)</ID>
|
||||
<ID>LongMethod:EditServerFragment.kt$EditServerFragment.<no name provided>$@Throws(Throwable::class) override fun doInBackground(): Boolean</ID>
|
||||
<ID>LongMethod:FileLoggerTree.kt$FileLoggerTree$ override fun log(priority: Int, tag: String?, message: String, t: Throwable?)</ID>
|
||||
<ID>LongMethod:FileLoggerTree.kt$FileLoggerTree$ private fun getNextLogFile()</ID>
|
||||
<ID>LongMethod:FilePickerAdapter.kt$FilePickerAdapter$fun createNewFolder()</ID>
|
||||
<ID>LongMethod:FilePickerAdapter.kt$FilePickerAdapter$private fun fileLister(currentDirectory: File)</ID>
|
||||
<ID>LongMethod:FilePickerAdapter.kt$FilePickerAdapter$private fun getKitKatStorageItems(storages: List<File>): LinkedList<FileListItem></ID>
|
||||
<ID>LongMethod:FilePickerAdapter.kt$FilePickerAdapter$private fun getStorageItems(): LinkedList<FileListItem></ID>
|
||||
<ID>LongMethod:FilePickerDialog.kt$FilePickerDialog$private fun initialize(context: Context)</ID>
|
||||
<ID>LongMethod:ImageLoaderProvider.kt$ImageLoaderProvider$@Synchronized fun getImageLoader(): ImageLoader</ID>
|
||||
<ID>LongMethod:LocalMediaPlayer.kt$LocalMediaPlayer$@Synchronized fun setPlayerState(playerState: PlayerState)</ID>
|
||||
<ID>LongMethod:LocalMediaPlayer.kt$LocalMediaPlayer$@Synchronized private fun doPlay(downloadFile: DownloadFile, position: Int, start: Boolean)</ID>
|
||||
<ID>LongMethod:LocalMediaPlayer.kt$LocalMediaPlayer$@Synchronized private fun setupNext(downloadFile: DownloadFile)</ID>
|
||||
<ID>LongMethod:LocalMediaPlayer.kt$LocalMediaPlayer$fun init()</ID>
|
||||
<ID>LongMethod:LocalMediaPlayer.kt$LocalMediaPlayer$fun release()</ID>
|
||||
<ID>LongMethod:LocalMediaPlayer.kt$LocalMediaPlayer.<no name provided>$override fun onCompletion(mediaPlayer: MediaPlayer)</ID>
|
||||
<ID>LongMethod:MediaPlayerService.kt$MediaPlayerService$ private fun buildForegroundNotification( playerState: PlayerState, currentPlaying: DownloadFile? ): Notification</ID>
|
||||
<ID>LongMethod:MediaPlayerService.kt$MediaPlayerService$@Synchronized fun setNextPlaying()</ID>
|
||||
<ID>LongMethod:MediaPlayerService.kt$MediaPlayerService$override fun onCreate()</ID>
|
||||
<ID>LongMethod:MediaPlayerService.kt$MediaPlayerService$private fun addActions( context: Context, notificationBuilder: NotificationCompat.Builder, playerState: PlayerState, song: MusicDirectory.Entry? ): IntArray</ID>
|
||||
<ID>LongMethod:MediaPlayerService.kt$MediaPlayerService$private fun generateAction(context: Context, requestCode: Int): NotificationCompat.Action?</ID>
|
||||
<ID>LongMethod:MediaPlayerService.kt$MediaPlayerService$private fun setupOnCurrentPlayingChangedHandler()</ID>
|
||||
<ID>LongMethod:MediaPlayerService.kt$MediaPlayerService$private fun setupOnPlayerStateChangedHandler()</ID>
|
||||
<ID>LongMethod:MediaPlayerService.kt$MediaPlayerService$private fun setupOnSongCompletedHandler()</ID>
|
||||
<ID>LongMethod:MediaPlayerService.kt$MediaPlayerService$private fun updateMediaSession(currentPlaying: DownloadFile?, playerState: PlayerState)</ID>
|
||||
<ID>LongMethod:NavigationActivity.kt$NavigationActivity$// TODO Test if this works with external Intents // android.intent.action.SEARCH and android.media.action.MEDIA_PLAY_FROM_SEARCH calls here override fun onNewIntent(intent: Intent?)</ID>
|
||||
<ID>LongMethod:NavigationActivity.kt$NavigationActivity$override fun onCreate(savedInstanceState: Bundle?)</ID>
|
||||
<ID>LongMethod:NavigationActivity.kt$NavigationActivity$private fun showNowPlaying()</ID>
|
||||
<ID>LongMethod:RESTMusicService.kt$RESTMusicService$@Throws(Exception::class) override fun getAvatar( username: String?, size: Int, saveToFile: Boolean, highQuality: Boolean ): Bitmap?</ID>
|
||||
<ID>LongMethod:RESTMusicService.kt$RESTMusicService$@Throws(Exception::class) override fun getCoverArt( entry: MusicDirectory.Entry?, size: Int, saveToFile: Boolean, highQuality: Boolean ): Bitmap?</ID>
|
||||
<ID>LongMethod:RESTMusicService.kt$RESTMusicService$@Throws(IOException::class) private fun savePlaylist( name: String?, playlist: MusicDirectory )</ID>
|
||||
<ID>LongMethod:RestErrorMapper.kt$ fun SubsonicRESTException.getLocalizedErrorMessage(context: Context): String</ID>
|
||||
<ID>LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$override fun onContextItemSelected(menuItem: MenuItem): Boolean</ID>
|
||||
<ID>LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?)</ID>
|
||||
<ID>LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun createHeader( entries: List<MusicDirectory.Entry>, name: CharSequence?, songCount: Int ): View?</ID>
|
||||
<ID>LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun downloadBackground(save: Boolean, songs: List<MusicDirectory.Entry?>)</ID>
|
||||
<ID>LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun enableButtons()</ID>
|
||||
<ID>LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun playAll(shuffle: Boolean = false, append: Boolean = false)</ID>
|
||||
<ID>LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun updateDisplay(refresh: Boolean)</ID>
|
||||
<ID>LongMethod:SelectAlbumFragment.kt$SelectAlbumFragment$private fun updateInterfaceWithEntries(musicDirectory: MusicDirectory)</ID>
|
||||
<ID>LongMethod:SelectAlbumModel.kt$SelectAlbumModel$suspend fun getAlbum(refresh: Boolean, id: String?, name: String?, parentId: String?)</ID>
|
||||
<ID>LongMethod:SelectAlbumModel.kt$SelectAlbumModel$suspend fun getAlbumList(albumListType: String, size: Int, offset: Int)</ID>
|
||||
<ID>LongMethod:SelectAlbumModel.kt$SelectAlbumModel$suspend fun getArtist(refresh: Boolean, id: String?, name: String?)</ID>
|
||||
<ID>LongMethod:SelectAlbumModel.kt$SelectAlbumModel$suspend fun getMusicDirectory( refresh: Boolean, id: String?, name: String?, parentId: String? )</ID>
|
||||
<ID>LongMethod:SelectArtistFragment.kt$SelectArtistFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?)</ID>
|
||||
<ID>LongMethod:SelectArtistFragment.kt$SelectArtistFragment$private fun onArtistMenuItemSelected(menuItem: MenuItem, artist: Artist): Boolean</ID>
|
||||
<ID>LongMethod:ServerRowAdapter.kt$ServerRowAdapter$ override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View?</ID>
|
||||
<ID>LongMethod:ServerRowAdapter.kt$ServerRowAdapter$ private fun popupMenuItemClick(menuItem: MenuItem, position: Int): Boolean</ID>
|
||||
<ID>LongMethod:ServerRowAdapter.kt$ServerRowAdapter$ private fun serverMenuClick(view: View, position: Int)</ID>
|
||||
<ID>LongMethod:ServerSelectorFragment.kt$ServerSelectorFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?)</ID>
|
||||
<ID>LongMethod:ServerSettingsModel.kt$ServerSettingsModel$ fun migrateFromPreferences(): Boolean</ID>
|
||||
<ID>LongMethod:ServerSettingsModel.kt$ServerSettingsModel$ private fun loadServerSettingFromPreferences( preferenceId: Int, serverId: Int, settings: SharedPreferences ): ServerSetting?</ID>
|
||||
<ID>LongMethod:ShareHandler.kt$ShareHandler$private fun showDialog( fragment: Fragment, shareDetails: ShareDetails, swipe: SwipeRefreshLayout?, cancellationToken: CancellationToken )</ID>
|
||||
<ID>LongMethod:SongView.kt$SongView$fun setLayout(song: MusicDirectory.Entry)</ID>
|
||||
<ID>LongMethod:SongView.kt$SongView$fun setSong(song: MusicDirectory.Entry, checkable: Boolean, draggable: Boolean)</ID>
|
||||
<ID>LongMethod:SongView.kt$SongView$private fun updateDownloadStatus(downloadFile: DownloadFile)</ID>
|
||||
<ID>LongMethod:SongView.kt$SongView$public override fun update()</ID>
|
||||
<ID>LongMethod:SubsonicUncaughtExceptionHandler.kt$SubsonicUncaughtExceptionHandler$override fun uncaughtException(thread: Thread, throwable: Throwable)</ID>
|
||||
<ID>LongMethod:UApp.kt$UApp$override fun onCreate()</ID>
|
||||
<ID>LongParameterList:ArtistRowAdapter.kt$ArtistRowAdapter$( private var artistList: List<Artist>, private var selectFolderHeader: SelectMusicFolderView?, val onArtistClick: (Artist) -> Unit, val onContextMenuClick: (MenuItem, Artist) -> Boolean, private val imageLoader: ImageLoader )</ID>
|
||||
<ID>LongMethod:TrackCollectionFragment.kt$TrackCollectionFragment$override fun onContextItemSelected(menuItem: MenuItem): Boolean</ID>
|
||||
<ID>LongMethod:TrackCollectionFragment.kt$TrackCollectionFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?)</ID>
|
||||
<ID>LongMethod:TrackCollectionFragment.kt$TrackCollectionFragment$private fun updateDisplay(refresh: Boolean)</ID>
|
||||
<ID>LongMethod:TrackCollectionFragment.kt$TrackCollectionFragment$private fun updateInterfaceWithEntries(musicDirectory: MusicDirectory)</ID>
|
||||
<ID>LongParameterList:DownloadHandler.kt$DownloadHandler$( fragment: Fragment, append: Boolean, save: Boolean, autoPlay: Boolean, playNext: Boolean, shuffle: Boolean, songs: List<MusicDirectory.Entry?> )</ID>
|
||||
<ID>LongParameterList:DownloadHandler.kt$DownloadHandler$( 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 )</ID>
|
||||
<ID>LongParameterList:DownloadHandler.kt$DownloadHandler$( fragment: Fragment, id: String, name: String?, save: Boolean, append: Boolean, autoplay: Boolean, shuffle: Boolean, background: Boolean, playNext: Boolean, unpin: Boolean )</ID>
|
||||
@ -188,11 +103,11 @@
|
||||
<ID>MagicNumber:MediaPlayerService.kt$MediaPlayerService.Companion$50L</ID>
|
||||
<ID>MagicNumber:RESTMusicService.kt$RESTMusicService$206</ID>
|
||||
<ID>MagicNumber:RESTMusicService.kt$RESTMusicService$5</ID>
|
||||
<ID>MagicNumber:SelectAlbumFragment.kt$SelectAlbumFragment$10</ID>
|
||||
<ID>MagicNumber:SelectMusicFolderView.kt$SelectMusicFolderView$10</ID>
|
||||
<ID>MagicNumber:SongView.kt$SongView$3</ID>
|
||||
<ID>MagicNumber:SongView.kt$SongView$4</ID>
|
||||
<ID>MagicNumber:SongView.kt$SongView$60</ID>
|
||||
<ID>MagicNumber:TrackCollectionFragment.kt$TrackCollectionFragment$10</ID>
|
||||
<ID>NestedBlockDepth:DownloadFile.kt$DownloadFile.DownloadTask$override fun execute()</ID>
|
||||
<ID>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 )</ID>
|
||||
<ID>NestedBlockDepth:MediaPlayerService.kt$MediaPlayerService$private fun setupOnSongCompletedHandler()</ID>
|
||||
@ -202,9 +117,9 @@
|
||||
<ID>ReturnCount:MediaPlayerService.kt$MediaPlayerService$private fun generateAction(context: Context, requestCode: Int): NotificationCompat.Action?</ID>
|
||||
<ID>ReturnCount:RESTMusicService.kt$RESTMusicService$@Throws(Exception::class) override fun getAvatar( username: String?, size: Int, saveToFile: Boolean, highQuality: Boolean ): Bitmap?</ID>
|
||||
<ID>ReturnCount:RESTMusicService.kt$RESTMusicService$@Throws(Exception::class) override fun getCoverArt( entry: MusicDirectory.Entry?, size: Int, saveToFile: Boolean, highQuality: Boolean ): Bitmap?</ID>
|
||||
<ID>ReturnCount:SelectAlbumFragment.kt$SelectAlbumFragment$override fun onContextItemSelected(menuItem: MenuItem): Boolean</ID>
|
||||
<ID>ReturnCount:SelectAlbumFragment.kt$SelectAlbumFragment$override fun onOptionsItemSelected(item: MenuItem): Boolean</ID>
|
||||
<ID>ReturnCount:ServerRowAdapter.kt$ServerRowAdapter$ private fun popupMenuItemClick(menuItem: MenuItem, position: Int): Boolean</ID>
|
||||
<ID>ReturnCount:TrackCollectionFragment.kt$TrackCollectionFragment$override fun onContextItemSelected(menuItem: MenuItem): Boolean</ID>
|
||||
<ID>ReturnCount:TrackCollectionFragment.kt$TrackCollectionFragment$override fun onOptionsItemSelected(item: MenuItem): Boolean</ID>
|
||||
<ID>SpreadOperator:MediaPlayerService.kt$MediaPlayerService$(*compactActions)</ID>
|
||||
<ID>SwallowedException:DownloadFile.kt$DownloadFile$catch (e: Exception) { Timber.w("Failed to set last-modified date on %s", file) }</ID>
|
||||
<ID>SwallowedException:DownloadFile.kt$DownloadFile$catch (ex: IOException) { Timber.w("Failed to rename file %s to %s", completeFile, saveFile) }</ID>
|
||||
@ -231,7 +146,7 @@
|
||||
<ID>TooManyFunctions:LocalMediaPlayer.kt$LocalMediaPlayer</ID>
|
||||
<ID>TooManyFunctions:MediaPlayerService.kt$MediaPlayerService : Service</ID>
|
||||
<ID>TooManyFunctions:RESTMusicService.kt$RESTMusicService : MusicService</ID>
|
||||
<ID>TooManyFunctions:SelectAlbumFragment.kt$SelectAlbumFragment : Fragment</ID>
|
||||
<ID>TooManyFunctions:TrackCollectionFragment.kt$TrackCollectionFragment : Fragment</ID>
|
||||
<ID>TopLevelPropertyNaming:SubsonicUncaughtExceptionHandler.kt$private const val filename = "ultrasonic-stacktrace.txt"</ID>
|
||||
<ID>UnusedPrivateMember:RESTMusicService.kt$RESTMusicService.Companion$private const val INDEXES_FOLDER_STORAGE_NAME = "indexes_folder"</ID>
|
||||
<ID>UselessCallOnNotNull:FileLoggerTree.kt$FileLoggerTree$fileList.isNullOrEmpty()</ID>
|
||||
|
@ -36,15 +36,6 @@ empty-blocks:
|
||||
|
||||
complexity:
|
||||
active: true
|
||||
LongMethod:
|
||||
threshold: 20
|
||||
LongParameterList:
|
||||
functionThreshold: 5
|
||||
constructorThreshold: 5
|
||||
LargeClass:
|
||||
threshold: 150
|
||||
ComplexMethod:
|
||||
threshold: 10
|
||||
TooManyFunctions:
|
||||
thresholdInFiles: 20
|
||||
thresholdInClasses: 20
|
||||
|
@ -4,7 +4,6 @@ import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
@ -142,71 +141,66 @@ public class MainFragment extends Fragment {
|
||||
}
|
||||
|
||||
list.setAdapter(adapter);
|
||||
list.setOnItemClickListener(new AdapterView.OnItemClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
|
||||
list.setOnItemClickListener((parent, view, position, id) -> {
|
||||
if (view == serverButton)
|
||||
{
|
||||
if (view == serverButton)
|
||||
{
|
||||
showServers();
|
||||
}
|
||||
else if (view == albumsNewestButton)
|
||||
{
|
||||
showAlbumList("newest", R.string.main_albums_newest);
|
||||
}
|
||||
else if (view == albumsRandomButton)
|
||||
{
|
||||
showAlbumList("random", R.string.main_albums_random);
|
||||
}
|
||||
else if (view == albumsHighestButton)
|
||||
{
|
||||
showAlbumList("highest", R.string.main_albums_highest);
|
||||
}
|
||||
else if (view == albumsRecentButton)
|
||||
{
|
||||
showAlbumList("recent", R.string.main_albums_recent);
|
||||
}
|
||||
else if (view == albumsFrequentButton)
|
||||
{
|
||||
showAlbumList("frequent", R.string.main_albums_frequent);
|
||||
}
|
||||
else if (view == albumsStarredButton)
|
||||
{
|
||||
showAlbumList(Constants.STARRED, R.string.main_albums_starred);
|
||||
}
|
||||
else if (view == albumsAlphaByNameButton)
|
||||
{
|
||||
showAlbumList(Constants.ALPHABETICAL_BY_NAME, R.string.main_albums_alphaByName);
|
||||
}
|
||||
else if (view == albumsAlphaByArtistButton)
|
||||
{
|
||||
showAlbumList("alphabeticalByArtist", R.string.main_albums_alphaByArtist);
|
||||
}
|
||||
else if (view == songsStarredButton)
|
||||
{
|
||||
showStarredSongs();
|
||||
}
|
||||
else if (view == artistsButton)
|
||||
{
|
||||
showArtists();
|
||||
}
|
||||
else if (view == albumsButton)
|
||||
{
|
||||
showAlbumList(Constants.ALPHABETICAL_BY_NAME, R.string.main_albums_title);
|
||||
}
|
||||
else if (view == randomSongsButton)
|
||||
{
|
||||
showRandomSongs();
|
||||
}
|
||||
else if (view == genresButton)
|
||||
{
|
||||
showGenres();
|
||||
}
|
||||
else if (view == videosButton)
|
||||
{
|
||||
showVideos();
|
||||
}
|
||||
showServers();
|
||||
}
|
||||
else if (view == albumsNewestButton)
|
||||
{
|
||||
showAlbumList("newest", R.string.main_albums_newest);
|
||||
}
|
||||
else if (view == albumsRandomButton)
|
||||
{
|
||||
showAlbumList("random", R.string.main_albums_random);
|
||||
}
|
||||
else if (view == albumsHighestButton)
|
||||
{
|
||||
showAlbumList("highest", R.string.main_albums_highest);
|
||||
}
|
||||
else if (view == albumsRecentButton)
|
||||
{
|
||||
showAlbumList("recent", R.string.main_albums_recent);
|
||||
}
|
||||
else if (view == albumsFrequentButton)
|
||||
{
|
||||
showAlbumList("frequent", R.string.main_albums_frequent);
|
||||
}
|
||||
else if (view == albumsStarredButton)
|
||||
{
|
||||
showAlbumList(Constants.STARRED, R.string.main_albums_starred);
|
||||
}
|
||||
else if (view == albumsAlphaByNameButton)
|
||||
{
|
||||
showAlbumList(Constants.ALPHABETICAL_BY_NAME, R.string.main_albums_alphaByName);
|
||||
}
|
||||
else if (view == albumsAlphaByArtistButton)
|
||||
{
|
||||
showAlbumList("alphabeticalByArtist", R.string.main_albums_alphaByArtist);
|
||||
}
|
||||
else if (view == songsStarredButton)
|
||||
{
|
||||
showStarredSongs();
|
||||
}
|
||||
else if (view == artistsButton)
|
||||
{
|
||||
showArtists();
|
||||
}
|
||||
else if (view == albumsButton)
|
||||
{
|
||||
showAlbumList(Constants.ALPHABETICAL_BY_NAME, R.string.main_albums_title);
|
||||
}
|
||||
else if (view == randomSongsButton)
|
||||
{
|
||||
showRandomSongs();
|
||||
}
|
||||
else if (view == genresButton)
|
||||
{
|
||||
showGenres();
|
||||
}
|
||||
else if (view == videosButton)
|
||||
{
|
||||
showVideos();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -219,21 +213,11 @@ public class MainFragment extends Fragment {
|
||||
currentSetting.getLdapSupport(), currentSetting.getMinimumApiVersion());
|
||||
}
|
||||
|
||||
private void showAlbumList(final String type, final int title)
|
||||
{
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, type);
|
||||
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, title);
|
||||
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, Util.getMaxAlbums());
|
||||
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0);
|
||||
Navigation.findNavController(getView()).navigate(R.id.mainToSelectAlbum, bundle);
|
||||
}
|
||||
|
||||
private void showStarredSongs()
|
||||
{
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt(Constants.INTENT_EXTRA_NAME_STARRED, 1);
|
||||
Navigation.findNavController(getView()).navigate(R.id.mainToSelectAlbum, bundle);
|
||||
Navigation.findNavController(getView()).navigate(R.id.mainToTrackCollection, bundle);
|
||||
}
|
||||
|
||||
private void showRandomSongs()
|
||||
@ -241,14 +225,23 @@ public class MainFragment extends Fragment {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt(Constants.INTENT_EXTRA_NAME_RANDOM, 1);
|
||||
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, Util.getMaxSongs());
|
||||
Navigation.findNavController(getView()).navigate(R.id.mainToSelectAlbum, bundle);
|
||||
Navigation.findNavController(getView()).navigate(R.id.mainToTrackCollection, bundle);
|
||||
}
|
||||
|
||||
private void showArtists()
|
||||
{
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, getContext().getResources().getString(R.string.main_artists_title));
|
||||
Navigation.findNavController(getView()).navigate(R.id.selectArtistFragment, bundle);
|
||||
Navigation.findNavController(getView()).navigate(R.id.mainToArtistList, bundle);
|
||||
}
|
||||
|
||||
private void showAlbumList(final String type, final int title) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, type);
|
||||
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, title);
|
||||
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, Util.getMaxAlbums());
|
||||
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0);
|
||||
Navigation.findNavController(getView()).navigate(R.id.mainToAlbumList, bundle);
|
||||
}
|
||||
|
||||
private void showGenres()
|
||||
@ -260,7 +253,7 @@ public class MainFragment extends Fragment {
|
||||
{
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt(Constants.INTENT_EXTRA_NAME_VIDEOS, 1);
|
||||
Navigation.findNavController(getView()).navigate(R.id.mainToSelectAlbum, bundle);
|
||||
Navigation.findNavController(getView()).navigate(R.id.mainToTrackCollection, bundle);
|
||||
}
|
||||
|
||||
private void showServers()
|
||||
|
@ -125,7 +125,7 @@ public class NowPlayingFragment extends Fragment {
|
||||
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, song.getAlbum());
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, song.getAlbum());
|
||||
Navigation.findNavController(getActivity(), R.id.nav_host_fragment).navigate(R.id.selectAlbumFragment, bundle);
|
||||
Navigation.findNavController(getActivity(), R.id.nav_host_fragment).navigate(R.id.trackCollectionFragment, bundle);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -105,7 +105,7 @@ public class PlaylistsFragment extends Fragment {
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_ID, playlist.getId());
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID, playlist.getId());
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME, playlist.getName());
|
||||
Navigation.findNavController(getView()).navigate(R.id.selectAlbumFragment, bundle);
|
||||
Navigation.findNavController(getView()).navigate(R.id.trackCollectionFragment, bundle);
|
||||
}
|
||||
});
|
||||
registerForContextMenu(playlistsListView);
|
||||
@ -154,7 +154,7 @@ public class PlaylistsFragment extends Fragment {
|
||||
if (ActiveServerProvider.Companion.isOffline()) inflater.inflate(R.menu.select_playlist_context_offline, menu);
|
||||
else inflater.inflate(R.menu.select_playlist_context, menu);
|
||||
|
||||
MenuItem downloadMenuItem = menu.findItem(R.id.album_menu_download);
|
||||
MenuItem downloadMenuItem = menu.findItem(R.id.playlist_menu_download);
|
||||
|
||||
if (downloadMenuItem != null)
|
||||
{
|
||||
@ -190,14 +190,14 @@ public class PlaylistsFragment extends Fragment {
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID, playlist.getId());
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME, playlist.getName());
|
||||
bundle.putBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, true);
|
||||
Navigation.findNavController(getView()).navigate(R.id.selectAlbumFragment, bundle);
|
||||
Navigation.findNavController(getView()).navigate(R.id.trackCollectionFragment, bundle);
|
||||
} else if (itemId == R.id.playlist_menu_play_shuffled) {
|
||||
bundle = new Bundle();
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID, playlist.getId());
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME, playlist.getName());
|
||||
bundle.putBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, true);
|
||||
bundle.putBoolean(Constants.INTENT_EXTRA_NAME_SHUFFLE, true);
|
||||
Navigation.findNavController(getView()).navigate(R.id.selectAlbumFragment, bundle);
|
||||
Navigation.findNavController(getView()).navigate(R.id.trackCollectionFragment, bundle);
|
||||
} else if (itemId == R.id.playlist_menu_delete) {
|
||||
deletePlaylist(playlist);
|
||||
} else if (itemId == R.id.playlist_info) {
|
||||
|
@ -76,7 +76,7 @@ public class PodcastFragment extends Fragment {
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_PODCAST_CHANNEL_ID, pc.getId());
|
||||
Navigation.findNavController(view).navigate(R.id.selectAlbumFragment, bundle);
|
||||
Navigation.findNavController(view).navigate(R.id.trackCollectionFragment, bundle);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -272,11 +272,11 @@ public class SearchFragment extends Fragment {
|
||||
}
|
||||
else
|
||||
{
|
||||
inflater.inflate(R.menu.select_album_context, menu);
|
||||
inflater.inflate(R.menu.generic_context_menu, menu);
|
||||
}
|
||||
|
||||
MenuItem shareButton = menu.findItem(R.id.menu_item_share);
|
||||
MenuItem downloadMenuItem = menu.findItem(R.id.album_menu_download);
|
||||
MenuItem downloadMenuItem = menu.findItem(R.id.menu_download);
|
||||
|
||||
if (downloadMenuItem != null)
|
||||
{
|
||||
@ -324,17 +324,17 @@ public class SearchFragment extends Fragment {
|
||||
List<MusicDirectory.Entry> songs = new ArrayList<>(1);
|
||||
|
||||
int itemId = menuItem.getItemId();
|
||||
if (itemId == R.id.album_menu_play_now) {
|
||||
if (itemId == R.id.menu_play_now) {
|
||||
downloadHandler.getValue().downloadRecursively(this, id, false, false, true, false, false, false, false, false);
|
||||
} else if (itemId == R.id.album_menu_play_next) {
|
||||
} else if (itemId == R.id.menu_play_next) {
|
||||
downloadHandler.getValue().downloadRecursively(this, id, false, true, false, true, false, true, false, false);
|
||||
} else if (itemId == R.id.album_menu_play_last) {
|
||||
} else if (itemId == R.id.menu_play_last) {
|
||||
downloadHandler.getValue().downloadRecursively(this, id, false, true, false, false, false, false, false, false);
|
||||
} else if (itemId == R.id.album_menu_pin) {
|
||||
} else if (itemId == R.id.menu_pin) {
|
||||
downloadHandler.getValue().downloadRecursively(this, id, true, true, false, false, false, false, false, false);
|
||||
} else if (itemId == R.id.album_menu_unpin) {
|
||||
} else if (itemId == R.id.menu_unpin) {
|
||||
downloadHandler.getValue().downloadRecursively(this, id, false, false, false, false, false, false, true, false);
|
||||
} else if (itemId == R.id.album_menu_download) {
|
||||
} else if (itemId == R.id.menu_download) {
|
||||
downloadHandler.getValue().downloadRecursively(this, id, false, false, false, false, true, false, false, false);
|
||||
} else if (itemId == R.id.song_menu_play_now) {
|
||||
if (entry != null) {
|
||||
|
@ -77,7 +77,7 @@ public class SelectGenreFragment extends Fragment {
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_GENRE_NAME, genre.getName());
|
||||
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, Util.getMaxSongs());
|
||||
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0);
|
||||
Navigation.findNavController(view).navigate(R.id.selectAlbumFragment, bundle);
|
||||
Navigation.findNavController(view).navigate(R.id.trackCollectionFragment, bundle);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -106,7 +106,7 @@ public class SharesFragment extends Fragment {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_SHARE_ID, share.getId());
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_SHARE_NAME, share.getName());
|
||||
Navigation.findNavController(view).navigate(R.id.selectAlbumFragment, bundle);
|
||||
Navigation.findNavController(view).navigate(R.id.trackCollectionFragment, bundle);
|
||||
}
|
||||
});
|
||||
registerForContextMenu(sharesListView);
|
||||
|
@ -58,6 +58,7 @@ public final class Constants
|
||||
public static final String INTENT_EXTRA_NAME_IS_ALBUM = "subsonic.isalbum";
|
||||
public static final String INTENT_EXTRA_NAME_VIDEOS = "subsonic.videos";
|
||||
public static final String INTENT_EXTRA_NAME_SHOW_PLAYER = "subsonic.showplayer";
|
||||
public static final String INTENT_EXTRA_NAME_APPEND = "subsonic.append";
|
||||
|
||||
// Names for Intent Actions
|
||||
public static final String CMD_PROCESS_KEYCODE = "org.moire.ultrasonic.CMD_PROCESS_KEYCODE";
|
||||
|
@ -71,7 +71,7 @@ public class AlbumView extends UpdateView
|
||||
|
||||
public void setLayout()
|
||||
{
|
||||
LayoutInflater.from(context).inflate(R.layout.album_list_item, this, true);
|
||||
LayoutInflater.from(context).inflate(R.layout.album_list_item_legacy, this, true);
|
||||
viewHolder = new EntryAdapter.AlbumViewHolder();
|
||||
viewHolder.title = findViewById(R.id.album_title);
|
||||
viewHolder.artist = findViewById(R.id.album_artist);
|
||||
|
@ -103,7 +103,7 @@ class NavigationActivity : AppCompatActivity() {
|
||||
appBarConfiguration = AppBarConfiguration(
|
||||
setOf(
|
||||
R.id.mainFragment,
|
||||
R.id.selectArtistFragment,
|
||||
R.id.mediaLibraryFragment,
|
||||
R.id.searchFragment,
|
||||
R.id.playlistsFragment,
|
||||
R.id.sharesFragment,
|
||||
|
@ -4,7 +4,6 @@ package org.moire.ultrasonic.di
|
||||
import kotlin.math.abs
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.android.viewmodel.dsl.viewModel
|
||||
import org.koin.core.qualifier.named
|
||||
import org.koin.dsl.module
|
||||
import org.moire.ultrasonic.BuildConfig
|
||||
@ -13,7 +12,6 @@ import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicClientConfiguration
|
||||
import org.moire.ultrasonic.cache.PermanentFileStorage
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||
import org.moire.ultrasonic.fragment.ArtistListModel
|
||||
import org.moire.ultrasonic.log.TimberOkHttpLogger
|
||||
import org.moire.ultrasonic.service.ApiCallResponseChecker
|
||||
import org.moire.ultrasonic.service.CachedMusicService
|
||||
@ -81,8 +79,6 @@ val musicServiceModule = module {
|
||||
|
||||
single { SubsonicImageLoader(androidContext(), get()) }
|
||||
|
||||
viewModel { ArtistListModel(get()) }
|
||||
|
||||
single { DownloadHandler(get(), get()) }
|
||||
single { NetworkAndStorageChecker(androidContext()) }
|
||||
single { VideoPlayer() }
|
||||
|
@ -0,0 +1,100 @@
|
||||
package org.moire.ultrasonic.fragment
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.koin.core.component.KoinApiExtension
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.domain.MusicDirectory
|
||||
import org.moire.ultrasonic.util.Constants
|
||||
|
||||
/**
|
||||
* Displays a list of Albums from the media library
|
||||
* TODO: Check refresh is working
|
||||
*/
|
||||
@KoinApiExtension
|
||||
class AlbumListFragment : GenericListFragment<MusicDirectory.Entry, AlbumRowAdapter>() {
|
||||
|
||||
/**
|
||||
* The ViewModel to use to get the data
|
||||
*/
|
||||
override val listModel: AlbumListModel by viewModels()
|
||||
|
||||
/**
|
||||
* The id of the main layout
|
||||
*/
|
||||
override val mainLayout: Int = R.layout.generic_list
|
||||
|
||||
/**
|
||||
* The id of the refresh view
|
||||
*/
|
||||
override val refreshListId: Int = R.id.generic_list_refresh
|
||||
|
||||
/**
|
||||
* The id of the RecyclerView
|
||||
*/
|
||||
override val recyclerViewId = R.id.generic_list_recycler
|
||||
|
||||
/**
|
||||
* The id of the target in the navigation graph where we should go,
|
||||
* after the user has clicked on an item
|
||||
*/
|
||||
override val itemClickTarget: Int = R.id.trackCollectionFragment
|
||||
|
||||
/**
|
||||
* The central function to pass a query to the model and return a LiveData object
|
||||
*/
|
||||
override fun getLiveData(args: Bundle?): LiveData<List<MusicDirectory.Entry>> {
|
||||
if (args == null) throw IllegalArgumentException("Required arguments are missing")
|
||||
|
||||
val refresh = args.getBoolean(Constants.INTENT_EXTRA_NAME_REFRESH)
|
||||
|
||||
return listModel.getAlbumList(refresh, refreshListView!!, args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the Adapter for the RecyclerView with a lazy delegate
|
||||
*/
|
||||
override val viewAdapter: AlbumRowAdapter by lazy {
|
||||
AlbumRowAdapter(
|
||||
liveDataItems.value ?: listOf(),
|
||||
{ entry -> onItemClick(entry) },
|
||||
{ menuItem, entry -> onContextMenuItemSelected(menuItem, entry) },
|
||||
imageLoaderProvider.getImageLoader(),
|
||||
onMusicFolderUpdate
|
||||
)
|
||||
}
|
||||
|
||||
val newBundleClone: Bundle
|
||||
get() = arguments?.clone() as Bundle
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Attach our onScrollListener
|
||||
listView = view.findViewById<RecyclerView>(recyclerViewId).apply {
|
||||
val scrollListener = object : EndlessScrollListener(viewManager) {
|
||||
override fun onLoadMore(page: Int, totalItemsCount: Int, view: RecyclerView?) {
|
||||
// Triggered only when new data needs to be appended to the list
|
||||
// Add whatever code is needed to append new items to the bottom of the list
|
||||
val appendArgs = newBundleClone
|
||||
appendArgs.putBoolean(Constants.INTENT_EXTRA_NAME_APPEND, true)
|
||||
getLiveData(appendArgs)
|
||||
}
|
||||
}
|
||||
addOnScrollListener(scrollListener)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemClick(item: MusicDirectory.Entry) {
|
||||
val bundle = Bundle()
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_ID, item.id)
|
||||
bundle.putBoolean(Constants.INTENT_EXTRA_NAME_IS_ALBUM, item.isDirectory)
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, item.title)
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_PARENT_ID, item.parent)
|
||||
findNavController().navigate(itemClickTarget, bundle)
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
package org.moire.ultrasonic.fragment
|
||||
|
||||
import android.app.Application
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import org.koin.core.component.KoinApiExtension
|
||||
import org.moire.ultrasonic.api.subsonic.models.AlbumListType
|
||||
import org.moire.ultrasonic.domain.MusicDirectory
|
||||
import org.moire.ultrasonic.service.MusicService
|
||||
import org.moire.ultrasonic.util.Constants
|
||||
import org.moire.ultrasonic.util.Util
|
||||
|
||||
@KoinApiExtension
|
||||
class AlbumListModel(application: Application) : GenericListModel(application) {
|
||||
|
||||
val albumList: MutableLiveData<List<MusicDirectory.Entry>> = MutableLiveData()
|
||||
private var loadedUntil: Int = 0
|
||||
|
||||
fun getAlbumList(
|
||||
refresh: Boolean,
|
||||
swipe: SwipeRefreshLayout,
|
||||
args: Bundle
|
||||
): LiveData<List<MusicDirectory.Entry>> {
|
||||
|
||||
backgroundLoadFromServer(refresh, swipe, args)
|
||||
return albumList
|
||||
}
|
||||
|
||||
override fun load(
|
||||
isOffline: Boolean,
|
||||
useId3Tags: Boolean,
|
||||
musicService: MusicService,
|
||||
refresh: Boolean,
|
||||
args: Bundle
|
||||
) {
|
||||
super.load(isOffline, useId3Tags, musicService, refresh, args)
|
||||
|
||||
val albumListType = args.getString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE)!!
|
||||
val size = args.getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0)
|
||||
var offset = args.getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0)
|
||||
val append = args.getBoolean(Constants.INTENT_EXTRA_NAME_APPEND, false)
|
||||
|
||||
val musicDirectory: MusicDirectory
|
||||
val musicFolderId = if (showSelectFolderHeader(args)) {
|
||||
activeServerProvider.getActiveServer().musicFolderId
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
// Handle the logic for endless scrolling:
|
||||
// If appending the existing list, set the offset from where to load
|
||||
if (append) offset += (size + loadedUntil)
|
||||
|
||||
if (useId3Tags) {
|
||||
musicDirectory = musicService.getAlbumList2(
|
||||
albumListType, size,
|
||||
offset, musicFolderId
|
||||
)
|
||||
} else {
|
||||
musicDirectory = musicService.getAlbumList(
|
||||
albumListType, size,
|
||||
offset, musicFolderId
|
||||
)
|
||||
}
|
||||
|
||||
currentListIsSortable = isCollectionSortable(albumListType)
|
||||
|
||||
if (append && albumList.value != null) {
|
||||
val list = ArrayList<MusicDirectory.Entry>()
|
||||
list.addAll(albumList.value!!)
|
||||
list.addAll(musicDirectory.getAllChild())
|
||||
albumList.postValue(list)
|
||||
} else {
|
||||
albumList.postValue(musicDirectory.getAllChild())
|
||||
}
|
||||
|
||||
loadedUntil = offset
|
||||
}
|
||||
|
||||
override fun showSelectFolderHeader(args: Bundle?): Boolean {
|
||||
if (args == null) return false
|
||||
|
||||
val albumListType = args.getString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE)!!
|
||||
|
||||
val isAlphabetical = (albumListType == AlbumListType.SORTED_BY_NAME.toString()) ||
|
||||
(albumListType == AlbumListType.SORTED_BY_ARTIST.toString())
|
||||
|
||||
return !isOffline() && !Util.getShouldUseId3Tags() && isAlphabetical
|
||||
}
|
||||
|
||||
private fun isCollectionSortable(albumListType: String): Boolean {
|
||||
return albumListType != "newest" && albumListType != "random" &&
|
||||
albumListType != "highest" && albumListType != "recent" &&
|
||||
albumListType != "frequent"
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* AlbumRowAdapter.kt
|
||||
* Copyright (C) 2009-2021 Ultrasonic developers
|
||||
*
|
||||
* Distributed under terms of the GNU GPLv3 license.
|
||||
*/
|
||||
|
||||
package org.moire.ultrasonic.fragment
|
||||
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.domain.MusicDirectory
|
||||
import org.moire.ultrasonic.util.ImageLoader
|
||||
|
||||
/**
|
||||
* Creates a Row in a RecyclerView which contains the details of an Album
|
||||
*/
|
||||
class AlbumRowAdapter(
|
||||
albumList: List<MusicDirectory.Entry>,
|
||||
onItemClick: (MusicDirectory.Entry) -> Unit,
|
||||
onContextMenuClick: (MenuItem, MusicDirectory.Entry) -> Boolean,
|
||||
private val imageLoader: ImageLoader,
|
||||
onMusicFolderUpdate: (String?) -> Unit
|
||||
) : GenericRowAdapter<MusicDirectory.Entry>(
|
||||
onItemClick,
|
||||
onContextMenuClick,
|
||||
imageLoader,
|
||||
onMusicFolderUpdate
|
||||
) {
|
||||
|
||||
override var itemList = albumList
|
||||
|
||||
// Set our layout files
|
||||
override val layout = R.layout.album_list_item
|
||||
override val contextMenuLayout = R.menu.artist_context_menu
|
||||
|
||||
// Sets the data to be displayed in the RecyclerView
|
||||
override fun setData(data: List<MusicDirectory.Entry>) {
|
||||
itemList = data
|
||||
super.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
if (holder is ViewHolder) {
|
||||
val listPosition = if (selectFolderHeader != null) position - 1 else position
|
||||
val entry = itemList[listPosition]
|
||||
holder.album.text = entry.title
|
||||
holder.artist.text = entry.artist
|
||||
holder.details.setOnClickListener { onItemClick(entry) }
|
||||
holder.details.setOnLongClickListener { view -> createPopupMenu(view, listPosition) }
|
||||
holder.coverArtId = entry.coverArt
|
||||
|
||||
imageLoader.loadImage(
|
||||
holder.coverArt,
|
||||
MusicDirectory.Entry().apply { coverArt = holder.coverArtId },
|
||||
false, 0, false, true, R.drawable.unknown_album
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
if (selectFolderHeader != null)
|
||||
return itemList.size + 1
|
||||
else
|
||||
return itemList.size
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the view properties of an Item row
|
||||
*/
|
||||
class ViewHolder(
|
||||
view: View
|
||||
) : RecyclerView.ViewHolder(view) {
|
||||
var album: TextView = view.findViewById(R.id.album_title)
|
||||
var artist: TextView = view.findViewById(R.id.album_artist)
|
||||
var details: LinearLayout = view.findViewById(R.id.row_album_details)
|
||||
var coverArt: ImageView = view.findViewById(R.id.album_coverart)
|
||||
var coverArtId: String? = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of our ViewHolder class
|
||||
*/
|
||||
override fun newViewHolder(view: View): RecyclerView.ViewHolder {
|
||||
return ViewHolder(view)
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package org.moire.ultrasonic.fragment
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.LiveData
|
||||
import org.koin.core.component.KoinApiExtension
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.domain.Artist
|
||||
import org.moire.ultrasonic.util.Constants
|
||||
|
||||
/**
|
||||
* Displays the list of Artists from the media library
|
||||
*/
|
||||
@KoinApiExtension
|
||||
class ArtistListFragment : GenericListFragment<Artist, ArtistRowAdapter>() {
|
||||
|
||||
/**
|
||||
* The ViewModel to use to get the data
|
||||
*/
|
||||
override val listModel: ArtistListModel by viewModels()
|
||||
|
||||
/**
|
||||
* The id of the main layout
|
||||
*/
|
||||
override val mainLayout = R.layout.generic_list
|
||||
|
||||
/**
|
||||
* The id of the refresh view
|
||||
*/
|
||||
override val refreshListId = R.id.generic_list_refresh
|
||||
|
||||
/**
|
||||
* The id of the RecyclerView
|
||||
*/
|
||||
override val recyclerViewId = R.id.generic_list_recycler
|
||||
|
||||
/**
|
||||
* The id of the target in the navigation graph where we should go,
|
||||
* after the user has clicked on an item
|
||||
*/
|
||||
override val itemClickTarget = R.id.selectArtistToSelectAlbum
|
||||
|
||||
/**
|
||||
* The central function to pass a query to the model and return a LiveData object
|
||||
*/
|
||||
override fun getLiveData(args: Bundle?): LiveData<List<Artist>> {
|
||||
val refresh = args?.getBoolean(Constants.INTENT_EXTRA_NAME_REFRESH) ?: false
|
||||
return listModel.getItems(refresh, refreshListView!!)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the Adapter for the RecyclerView with a lazy delegate
|
||||
*/
|
||||
override val viewAdapter: ArtistRowAdapter by lazy {
|
||||
ArtistRowAdapter(
|
||||
liveDataItems.value ?: listOf(),
|
||||
{ entry -> onItemClick(entry) },
|
||||
{ menuItem, entry -> onContextMenuItemSelected(menuItem, entry) },
|
||||
imageLoaderProvider.getImageLoader(),
|
||||
onMusicFolderUpdate
|
||||
)
|
||||
}
|
||||
}
|
@ -18,90 +18,49 @@
|
||||
*/
|
||||
package org.moire.ultrasonic.fragment
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.app.Application
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||
import org.koin.core.component.KoinApiExtension
|
||||
import org.moire.ultrasonic.domain.Artist
|
||||
import org.moire.ultrasonic.domain.MusicFolder
|
||||
import org.moire.ultrasonic.service.CommunicationErrorHandler
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory
|
||||
import org.moire.ultrasonic.util.Util
|
||||
import org.moire.ultrasonic.service.MusicService
|
||||
|
||||
/**
|
||||
* Provides ViewModel which contains the list of available Artists
|
||||
*/
|
||||
class ArtistListModel(
|
||||
private val activeServerProvider: ActiveServerProvider
|
||||
) : ViewModel() {
|
||||
private val musicFolders: MutableLiveData<List<MusicFolder>> = MutableLiveData()
|
||||
@KoinApiExtension
|
||||
class ArtistListModel(application: Application) : GenericListModel(application) {
|
||||
private val artists: MutableLiveData<List<Artist>> = MutableLiveData()
|
||||
|
||||
/**
|
||||
* Retrieves the available Artists in a LiveData
|
||||
* Retrieves all available Artists in a LiveData
|
||||
*/
|
||||
fun getArtists(refresh: Boolean, swipe: SwipeRefreshLayout): LiveData<List<Artist>> {
|
||||
fun getItems(refresh: Boolean, swipe: SwipeRefreshLayout): LiveData<List<Artist>> {
|
||||
backgroundLoadFromServer(refresh, swipe)
|
||||
return artists
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the available Music Folders in a LiveData
|
||||
*/
|
||||
fun getMusicFolders(): LiveData<List<MusicFolder>> {
|
||||
return musicFolders
|
||||
override fun load(
|
||||
isOffline: Boolean,
|
||||
useId3Tags: Boolean,
|
||||
musicService: MusicService,
|
||||
refresh: Boolean,
|
||||
args: Bundle
|
||||
) {
|
||||
super.load(isOffline, useId3Tags, musicService, refresh, args)
|
||||
|
||||
val musicFolderId = activeServer.musicFolderId
|
||||
|
||||
val result = if (!isOffline && useId3Tags)
|
||||
musicService.getArtists(refresh)
|
||||
else musicService.getIndexes(musicFolderId, refresh)
|
||||
|
||||
val retrievedArtists: MutableList<Artist> =
|
||||
ArrayList(result.shortcuts.size + result.artists.size)
|
||||
retrievedArtists.addAll(result.shortcuts)
|
||||
retrievedArtists.addAll(result.artists)
|
||||
artists.postValue(retrievedArtists)
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the cached Artists from the server
|
||||
*/
|
||||
fun refresh(swipe: SwipeRefreshLayout) {
|
||||
backgroundLoadFromServer(true, swipe)
|
||||
}
|
||||
|
||||
private fun backgroundLoadFromServer(refresh: Boolean, swipe: SwipeRefreshLayout) {
|
||||
viewModelScope.launch {
|
||||
swipe.isRefreshing = true
|
||||
loadFromServer(refresh, swipe)
|
||||
swipe.isRefreshing = false
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadFromServer(refresh: Boolean, swipe: SwipeRefreshLayout) =
|
||||
withContext(Dispatchers.IO) {
|
||||
val musicService = MusicServiceFactory.getMusicService()
|
||||
val isOffline = ActiveServerProvider.isOffline()
|
||||
val useId3Tags = Util.getShouldUseId3Tags()
|
||||
|
||||
try {
|
||||
if (!isOffline && !useId3Tags) {
|
||||
musicFolders.postValue(
|
||||
musicService.getMusicFolders(refresh)
|
||||
)
|
||||
}
|
||||
|
||||
val musicFolderId = activeServerProvider.getActiveServer().musicFolderId
|
||||
|
||||
val result = if (!isOffline && useId3Tags)
|
||||
musicService.getArtists(refresh)
|
||||
else musicService.getIndexes(musicFolderId, refresh)
|
||||
|
||||
val retrievedArtists: MutableList<Artist> =
|
||||
ArrayList(result.shortcuts.size + result.artists.size)
|
||||
retrievedArtists.addAll(result.shortcuts)
|
||||
retrievedArtists.addAll(result.artists)
|
||||
artists.postValue(retrievedArtists)
|
||||
} catch (exception: Exception) {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
CommunicationErrorHandler.handleError(exception, swipe.context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,102 +1,62 @@
|
||||
/*
|
||||
This file is part of Subsonic.
|
||||
|
||||
Subsonic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Subsonic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2020 (C) Jozsef Varga
|
||||
* ArtistRowAdapter.kt
|
||||
* Copyright (C) 2009-2021 Ultrasonic developers
|
||||
*
|
||||
* Distributed under terms of the GNU GPLv3 license.
|
||||
*/
|
||||
|
||||
package org.moire.ultrasonic.fragment
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.PopupMenu
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView.SectionedAdapter
|
||||
import java.text.Collator
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
|
||||
import org.moire.ultrasonic.domain.Artist
|
||||
import org.moire.ultrasonic.domain.MusicDirectory
|
||||
import org.moire.ultrasonic.util.ImageLoader
|
||||
import org.moire.ultrasonic.util.Util
|
||||
import org.moire.ultrasonic.view.SelectMusicFolderView
|
||||
|
||||
/**
|
||||
* Creates a Row in a RecyclerView which contains the details of an Artist
|
||||
*/
|
||||
class ArtistRowAdapter(
|
||||
private var artistList: List<Artist>,
|
||||
private var selectFolderHeader: SelectMusicFolderView?,
|
||||
val onArtistClick: (Artist) -> Unit,
|
||||
val onContextMenuClick: (MenuItem, Artist) -> Boolean,
|
||||
private val imageLoader: ImageLoader
|
||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), SectionedAdapter {
|
||||
artistList: List<Artist>,
|
||||
onItemClick: (Artist) -> Unit,
|
||||
onContextMenuClick: (MenuItem, Artist) -> Boolean,
|
||||
private val imageLoader: ImageLoader,
|
||||
onMusicFolderUpdate: (String?) -> Unit
|
||||
) : GenericRowAdapter<Artist>(
|
||||
onItemClick,
|
||||
onContextMenuClick,
|
||||
imageLoader,
|
||||
onMusicFolderUpdate
|
||||
),
|
||||
SectionedAdapter {
|
||||
|
||||
override var itemList = artistList
|
||||
|
||||
// Set our layout files
|
||||
override val layout = R.layout.artist_list_item
|
||||
override val contextMenuLayout = R.menu.artist_context_menu
|
||||
|
||||
/**
|
||||
* Sets the data to be displayed in the RecyclerView
|
||||
*/
|
||||
fun setData(data: List<Artist>) {
|
||||
artistList = data.sortedWith(compareBy(Collator.getInstance()) { t -> t.name })
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the view properties of an Artist row
|
||||
*/
|
||||
class ArtistViewHolder(
|
||||
itemView: View
|
||||
) : RecyclerView.ViewHolder(itemView) {
|
||||
var section: TextView = itemView.findViewById(R.id.row_section)
|
||||
var textView: TextView = itemView.findViewById(R.id.row_artist_name)
|
||||
var layout: RelativeLayout = itemView.findViewById(R.id.row_artist_layout)
|
||||
var coverArt: ImageView = itemView.findViewById(R.id.artist_coverart)
|
||||
var coverArtId: String? = null
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): RecyclerView.ViewHolder {
|
||||
if (viewType == TYPE_ITEM) {
|
||||
val row = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.artist_list_item, parent, false)
|
||||
return ArtistViewHolder(row)
|
||||
}
|
||||
return selectFolderHeader!!
|
||||
}
|
||||
|
||||
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
|
||||
if ((holder is ArtistViewHolder) && (holder.coverArtId != null)) {
|
||||
imageLoader.cancel(holder.coverArtId)
|
||||
}
|
||||
super.onViewRecycled(holder)
|
||||
override fun setData(data: List<Artist>) {
|
||||
itemList = data.sortedWith(compareBy(Collator.getInstance()) { t -> t.name })
|
||||
super.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
if (holder is ArtistViewHolder) {
|
||||
if (holder is ViewHolder) {
|
||||
val listPosition = if (selectFolderHeader != null) position - 1 else position
|
||||
holder.textView.text = artistList[listPosition].name
|
||||
holder.textView.text = itemList[listPosition].name
|
||||
holder.section.text = getSectionForArtist(listPosition)
|
||||
holder.layout.setOnClickListener { onArtistClick(artistList[listPosition]) }
|
||||
holder.layout.setOnClickListener { onItemClick(itemList[listPosition]) }
|
||||
holder.layout.setOnLongClickListener { view -> createPopupMenu(view, listPosition) }
|
||||
holder.coverArtId = artistList[listPosition].coverArt
|
||||
holder.coverArtId = itemList[listPosition].coverArt
|
||||
|
||||
if (Util.getShouldShowArtistPicture()) {
|
||||
holder.coverArt.visibility = View.VISIBLE
|
||||
@ -111,15 +71,6 @@ class ArtistRowAdapter(
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount() = if (selectFolderHeader != null)
|
||||
artistList.size + 1
|
||||
else
|
||||
artistList.size
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return if (position == 0 && selectFolderHeader != null) TYPE_HEADER else TYPE_ITEM
|
||||
}
|
||||
|
||||
override fun getSectionName(position: Int): String {
|
||||
var listPosition = if (selectFolderHeader != null) position - 1 else position
|
||||
|
||||
@ -127,18 +78,18 @@ class ArtistRowAdapter(
|
||||
// scrolled up to the "Select Folder" row
|
||||
if (listPosition < 0) listPosition = 0
|
||||
|
||||
return getSectionFromName(artistList[listPosition].name ?: " ")
|
||||
return getSectionFromName(itemList[listPosition].name ?: " ")
|
||||
}
|
||||
|
||||
private fun getSectionForArtist(artistPosition: Int): String {
|
||||
if (artistPosition == 0)
|
||||
return getSectionFromName(artistList[artistPosition].name ?: " ")
|
||||
return getSectionFromName(itemList[artistPosition].name ?: " ")
|
||||
|
||||
val previousArtistSection = getSectionFromName(
|
||||
artistList[artistPosition - 1].name ?: " "
|
||||
itemList[artistPosition - 1].name ?: " "
|
||||
)
|
||||
val currentArtistSection = getSectionFromName(
|
||||
artistList[artistPosition].name ?: " "
|
||||
itemList[artistPosition].name ?: " "
|
||||
)
|
||||
|
||||
return if (previousArtistSection == currentArtistSection) "" else currentArtistSection
|
||||
@ -150,23 +101,10 @@ class ArtistRowAdapter(
|
||||
return section.toString()
|
||||
}
|
||||
|
||||
private fun createPopupMenu(view: View, position: Int): Boolean {
|
||||
val popup = PopupMenu(view.context, view)
|
||||
val inflater: MenuInflater = popup.menuInflater
|
||||
inflater.inflate(R.menu.select_artist_context, popup.menu)
|
||||
|
||||
val downloadMenuItem = popup.menu.findItem(R.id.artist_menu_download)
|
||||
downloadMenuItem?.isVisible = !isOffline()
|
||||
|
||||
popup.setOnMenuItemClickListener { menuItem ->
|
||||
onContextMenuClick(menuItem, artistList[position])
|
||||
}
|
||||
popup.show()
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TYPE_HEADER = 0
|
||||
private const val TYPE_ITEM = 1
|
||||
/**
|
||||
* Creates an instance of our ViewHolder class
|
||||
*/
|
||||
override fun newViewHolder(view: View): RecyclerView.ViewHolder {
|
||||
return ViewHolder(view)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,126 @@
|
||||
package org.moire.ultrasonic.fragment
|
||||
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||
|
||||
/*
|
||||
* An abstract ScrollListener, which can be extended to provide endless scrolling capabilities
|
||||
*/
|
||||
abstract class EndlessScrollListener : RecyclerView.OnScrollListener {
|
||||
// The minimum amount of items to have below your current scroll position
|
||||
// before loading more.
|
||||
private var treshold = VISIBLE_TRESHOLD
|
||||
|
||||
// The current offset index of data you have loaded
|
||||
private var currentPage = 0
|
||||
|
||||
// The total number of items in the dataset after the last load
|
||||
private var previousTotalItemCount = 0
|
||||
|
||||
// True if we are still waiting for the last set of data to load.
|
||||
private var loading = true
|
||||
|
||||
// Sets the starting page index
|
||||
private val startingPageIndex = 0
|
||||
var thisManager: RecyclerView.LayoutManager
|
||||
|
||||
constructor(layoutManager: LinearLayoutManager) {
|
||||
thisManager = layoutManager
|
||||
}
|
||||
|
||||
@Suppress("Unused")
|
||||
constructor(layoutManager: GridLayoutManager) {
|
||||
thisManager = layoutManager
|
||||
treshold *= layoutManager.spanCount
|
||||
}
|
||||
|
||||
@Suppress("Unused")
|
||||
constructor(layoutManager: StaggeredGridLayoutManager) {
|
||||
thisManager = layoutManager
|
||||
treshold *= layoutManager.spanCount
|
||||
}
|
||||
|
||||
private fun getLastVisibleItem(lastVisibleItemPositions: IntArray): Int {
|
||||
var maxSize = 0
|
||||
for (i in lastVisibleItemPositions.indices) {
|
||||
if (i == 0) {
|
||||
maxSize = lastVisibleItemPositions[i]
|
||||
} else if (lastVisibleItemPositions[i] > maxSize) {
|
||||
maxSize = lastVisibleItemPositions[i]
|
||||
}
|
||||
}
|
||||
return maxSize
|
||||
}
|
||||
|
||||
// This happens many times a second during a scroll, so be wary of the code you place here.
|
||||
// We are given a few useful parameters to help us work out if we need to load some more data,
|
||||
// but first we check if we are waiting for the previous load to finish.
|
||||
override fun onScrolled(view: RecyclerView, dx: Int, dy: Int) {
|
||||
var lastVisibleItemPosition = 0
|
||||
|
||||
val thisManager: RecyclerView.LayoutManager = thisManager
|
||||
val totalItemCount = thisManager.itemCount
|
||||
|
||||
when (thisManager) {
|
||||
is StaggeredGridLayoutManager -> {
|
||||
val lastVisibleItemPositions =
|
||||
thisManager.findLastVisibleItemPositions(null)
|
||||
// get maximum element within the list
|
||||
lastVisibleItemPosition = getLastVisibleItem(lastVisibleItemPositions)
|
||||
}
|
||||
is GridLayoutManager -> {
|
||||
lastVisibleItemPosition =
|
||||
thisManager.findLastVisibleItemPosition()
|
||||
}
|
||||
is LinearLayoutManager -> {
|
||||
lastVisibleItemPosition =
|
||||
thisManager.findLastVisibleItemPosition()
|
||||
}
|
||||
}
|
||||
|
||||
// If the total item count is zero and the previous isn't, assume the
|
||||
// list is invalidated and should be reset back to initial state
|
||||
if (totalItemCount < previousTotalItemCount) {
|
||||
currentPage = startingPageIndex
|
||||
previousTotalItemCount = totalItemCount
|
||||
if (totalItemCount == 0) {
|
||||
loading = true
|
||||
}
|
||||
}
|
||||
// If it’s still loading, we check to see if the dataset count has
|
||||
// changed, if so we conclude it has finished loading and update the current page
|
||||
// number and total item count.
|
||||
if (loading && totalItemCount > previousTotalItemCount) {
|
||||
loading = false
|
||||
previousTotalItemCount = totalItemCount
|
||||
}
|
||||
|
||||
// If it isn’t currently loading, we check to see if we have breached
|
||||
// the visibleThreshold and need to reload more data.
|
||||
// If we do need to reload some more data, we execute onLoadMore to fetch the data.
|
||||
// threshold should reflect how many total columns there are too
|
||||
if (!loading && lastVisibleItemPosition + treshold > totalItemCount) {
|
||||
currentPage++
|
||||
onLoadMore(currentPage, totalItemCount, view)
|
||||
loading = true
|
||||
}
|
||||
}
|
||||
|
||||
// Call this method whenever performing new searches
|
||||
fun resetState() {
|
||||
currentPage = startingPageIndex
|
||||
previousTotalItemCount = 0
|
||||
loading = true
|
||||
}
|
||||
|
||||
// Defines the process for actually loading more data based on page
|
||||
abstract fun onLoadMore(page: Int, totalItemsCount: Int, view: RecyclerView?)
|
||||
|
||||
companion object {
|
||||
// The minimum amount of items to have below your current scroll position
|
||||
// before loading more.
|
||||
const val VISIBLE_TRESHOLD = 7
|
||||
}
|
||||
}
|
@ -0,0 +1,275 @@
|
||||
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.android.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.component.KoinApiExtension
|
||||
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.MusicFolder
|
||||
import org.moire.ultrasonic.subsonic.DownloadHandler
|
||||
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
|
||||
import org.moire.ultrasonic.util.Constants
|
||||
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)
|
||||
*/
|
||||
@KoinApiExtension
|
||||
abstract class GenericListFragment<T : GenericEntry, TA : GenericRowAdapter<T>> : 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<List<T>>
|
||||
|
||||
/**
|
||||
* The central function to pass a query to the model and return a LiveData object
|
||||
*/
|
||||
abstract fun getLiveData(args: Bundle? = null): LiveData<List<T>>
|
||||
|
||||
/**
|
||||
* 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<MusicFolder> ->
|
||||
viewAdapter.setFolderList(folders, listModel.activeServer.musicFolderId)
|
||||
Unit
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() && !Util.getShouldUseId3Tags()
|
||||
}
|
||||
|
||||
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.setData(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<RecyclerView>(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)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
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
|
||||
}
|
||||
|
||||
open 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)
|
||||
}
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
package org.moire.ultrasonic.fragment
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import java.net.ConnectException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
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.data.ActiveServerProvider
|
||||
import org.moire.ultrasonic.data.ServerSetting
|
||||
import org.moire.ultrasonic.domain.MusicFolder
|
||||
import org.moire.ultrasonic.service.CommunicationErrorHandler
|
||||
import org.moire.ultrasonic.service.MusicService
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory
|
||||
import org.moire.ultrasonic.util.Util
|
||||
|
||||
/**
|
||||
* An abstract Model, which can be extended to retrieve a list of items from the API
|
||||
*/
|
||||
@KoinApiExtension
|
||||
open class GenericListModel(application: Application) :
|
||||
AndroidViewModel(application), KoinComponent {
|
||||
|
||||
val activeServerProvider: ActiveServerProvider by inject()
|
||||
|
||||
val activeServer: ServerSetting
|
||||
get() = activeServerProvider.getActiveServer()
|
||||
|
||||
val context: Context
|
||||
get() = getApplication<Application>().applicationContext
|
||||
|
||||
var currentListIsSortable = true
|
||||
var showHeader = true
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
open fun showSelectFolderHeader(args: Bundle?): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
internal val musicFolders: MutableLiveData<List<MusicFolder>> = MutableLiveData()
|
||||
|
||||
/**
|
||||
* Helper function to check online status
|
||||
*/
|
||||
fun isOffline(): Boolean {
|
||||
return ActiveServerProvider.isOffline()
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the cached items from the server
|
||||
*/
|
||||
fun refresh(swipe: SwipeRefreshLayout, bundle: Bundle?) {
|
||||
backgroundLoadFromServer(true, swipe, bundle ?: Bundle())
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a load() and notify the UI that we are loading
|
||||
*/
|
||||
fun backgroundLoadFromServer(
|
||||
refresh: Boolean,
|
||||
swipe: SwipeRefreshLayout,
|
||||
bundle: Bundle = Bundle()
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
swipe.isRefreshing = true
|
||||
loadFromServer(refresh, swipe, bundle)
|
||||
swipe.isRefreshing = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the load() function with error handling
|
||||
*/
|
||||
suspend fun loadFromServer(refresh: Boolean, swipe: SwipeRefreshLayout, bundle: Bundle) =
|
||||
withContext(Dispatchers.IO) {
|
||||
val musicService = MusicServiceFactory.getMusicService()
|
||||
val isOffline = ActiveServerProvider.isOffline()
|
||||
val useId3Tags = Util.getShouldUseId3Tags()
|
||||
|
||||
try {
|
||||
load(isOffline, useId3Tags, musicService, refresh, bundle)
|
||||
} catch (exception: ConnectException) {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
CommunicationErrorHandler.handleError(exception, swipe.context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the central function you need to implement if you want to extend this class
|
||||
*/
|
||||
open fun load(
|
||||
isOffline: Boolean,
|
||||
useId3Tags: Boolean,
|
||||
musicService: MusicService,
|
||||
refresh: Boolean,
|
||||
args: Bundle
|
||||
) {
|
||||
// Update the list of available folders if enabled
|
||||
if (showSelectFolderHeader(args) && !isOffline && !useId3Tags) {
|
||||
musicFolders.postValue(
|
||||
musicService.getMusicFolders(refresh)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the available Music Folders in a LiveData
|
||||
*/
|
||||
fun getMusicFolders(): LiveData<List<MusicFolder>> {
|
||||
return musicFolders
|
||||
}
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* GenericRowAdapter.kt
|
||||
* Copyright (C) 2009-2021 Ultrasonic developers
|
||||
*
|
||||
* Distributed under terms of the GNU GPLv3 license.
|
||||
*/
|
||||
|
||||
package org.moire.ultrasonic.fragment
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.PopupMenu
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||
import org.moire.ultrasonic.domain.MusicFolder
|
||||
import org.moire.ultrasonic.util.ImageLoader
|
||||
import org.moire.ultrasonic.view.SelectMusicFolderView
|
||||
|
||||
/*
|
||||
* An abstract Adapter, which can be extended to display a List of <T> in a RecyclerView
|
||||
*/
|
||||
abstract class GenericRowAdapter<T>(
|
||||
val onItemClick: (T) -> Unit,
|
||||
val onContextMenuClick: (MenuItem, T) -> Boolean,
|
||||
private val imageLoader: ImageLoader,
|
||||
private val onMusicFolderUpdate: (String?) -> Unit
|
||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
open var itemList: List<T> = listOf()
|
||||
protected abstract val layout: Int
|
||||
protected abstract val contextMenuLayout: Int
|
||||
|
||||
var folderHeaderEnabled: Boolean = true
|
||||
var selectFolderHeader: SelectMusicFolderView? = null
|
||||
var musicFolders: List<MusicFolder> = listOf()
|
||||
var selectedFolder: String? = null
|
||||
|
||||
/**
|
||||
* Sets the data to be displayed in the RecyclerView
|
||||
*/
|
||||
open fun setData(data: List<T>) {
|
||||
itemList = data
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the content and state of the music folder selector row
|
||||
*/
|
||||
fun setFolderList(changedFolders: List<MusicFolder>, selectedId: String?) {
|
||||
musicFolders = changedFolders
|
||||
selectedFolder = selectedId
|
||||
|
||||
selectFolderHeader?.setData(
|
||||
selectedFolder,
|
||||
musicFolders
|
||||
)
|
||||
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
open fun newViewHolder(view: View): RecyclerView.ViewHolder {
|
||||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): RecyclerView.ViewHolder {
|
||||
if (viewType == TYPE_ITEM) {
|
||||
val row = LayoutInflater.from(parent.context)
|
||||
.inflate(layout, parent, false)
|
||||
return newViewHolder(row)
|
||||
} else {
|
||||
val row = LayoutInflater.from(parent.context)
|
||||
.inflate(
|
||||
R.layout.select_folder_header, parent, false
|
||||
)
|
||||
selectFolderHeader = SelectMusicFolderView(parent.context, row, onMusicFolderUpdate)
|
||||
|
||||
if (musicFolders.isNotEmpty()) {
|
||||
selectFolderHeader?.setData(
|
||||
selectedFolder,
|
||||
musicFolders
|
||||
)
|
||||
}
|
||||
|
||||
return selectFolderHeader!!
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
|
||||
if ((holder is ViewHolder) && (holder.coverArtId != null)) {
|
||||
imageLoader.cancel(holder.coverArtId)
|
||||
}
|
||||
super.onViewRecycled(holder)
|
||||
}
|
||||
|
||||
abstract override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int)
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
if (selectFolderHeader != null)
|
||||
return itemList.size + 1
|
||||
else
|
||||
return itemList.size
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return if (position == 0 && folderHeaderEnabled) TYPE_HEADER else TYPE_ITEM
|
||||
}
|
||||
|
||||
internal fun createPopupMenu(view: View, position: Int): Boolean {
|
||||
val popup = PopupMenu(view.context, view)
|
||||
val inflater: MenuInflater = popup.menuInflater
|
||||
inflater.inflate(contextMenuLayout, popup.menu)
|
||||
|
||||
val downloadMenuItem = popup.menu.findItem(R.id.menu_download)
|
||||
downloadMenuItem?.isVisible = !ActiveServerProvider.isOffline()
|
||||
|
||||
popup.setOnMenuItemClickListener { menuItem ->
|
||||
onContextMenuClick(menuItem, itemList[position])
|
||||
}
|
||||
popup.show()
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the view properties of an Item row
|
||||
*/
|
||||
class ViewHolder(
|
||||
itemView: View
|
||||
) : RecyclerView.ViewHolder(itemView) {
|
||||
var section: TextView = itemView.findViewById(R.id.row_section)
|
||||
var textView: TextView = itemView.findViewById(R.id.row_artist_name)
|
||||
var layout: RelativeLayout = itemView.findViewById(R.id.row_artist_layout)
|
||||
var coverArt: ImageView = itemView.findViewById(R.id.artist_coverart)
|
||||
var coverArtId: String? = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal const val TYPE_HEADER = 0
|
||||
internal const val TYPE_ITEM = 1
|
||||
}
|
||||
}
|
@ -1,222 +0,0 @@
|
||||
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.lifecycle.Observer
|
||||
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.android.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.fragment.FragmentTitle.Companion.setTitle
|
||||
import org.moire.ultrasonic.subsonic.DownloadHandler
|
||||
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
|
||||
import org.moire.ultrasonic.util.Constants
|
||||
import org.moire.ultrasonic.util.Util
|
||||
import org.moire.ultrasonic.view.SelectMusicFolderView
|
||||
|
||||
/**
|
||||
* Displays the list of Artists from the media library
|
||||
*/
|
||||
class SelectArtistFragment : Fragment() {
|
||||
private val activeServerProvider: ActiveServerProvider by inject()
|
||||
private val serverSettingsModel: ServerSettingsModel by viewModel()
|
||||
private val artistListModel: ArtistListModel by viewModel()
|
||||
private val imageLoaderProvider: ImageLoaderProvider by inject()
|
||||
private val downloadHandler: DownloadHandler by inject()
|
||||
|
||||
private var refreshArtistListView: SwipeRefreshLayout? = null
|
||||
private var artistListView: RecyclerView? = null
|
||||
private lateinit var viewManager: RecyclerView.LayoutManager
|
||||
private lateinit var viewAdapter: ArtistRowAdapter
|
||||
private var selectFolderHeader: SelectMusicFolderView? = null
|
||||
|
||||
@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(R.layout.select_artist, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
refreshArtistListView = view.findViewById(R.id.select_artist_refresh)
|
||||
refreshArtistListView!!.setOnRefreshListener {
|
||||
artistListModel.refresh(refreshArtistListView!!)
|
||||
}
|
||||
|
||||
if (!ActiveServerProvider.isOffline() &&
|
||||
!Util.getShouldUseId3Tags()
|
||||
) {
|
||||
selectFolderHeader = SelectMusicFolderView(
|
||||
requireContext(), view as ViewGroup,
|
||||
{ selectedFolderId ->
|
||||
if (!ActiveServerProvider.isOffline()) {
|
||||
val currentSetting = activeServerProvider.getActiveServer()
|
||||
currentSetting.musicFolderId = selectedFolderId
|
||||
serverSettingsModel.updateItem(currentSetting)
|
||||
}
|
||||
viewAdapter.notifyDataSetChanged()
|
||||
artistListModel.refresh(refreshArtistListView!!)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val title = arguments?.getString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE)
|
||||
|
||||
if (title == null) {
|
||||
setTitle(
|
||||
this,
|
||||
if (ActiveServerProvider.isOffline())
|
||||
R.string.music_library_label_offline
|
||||
else R.string.music_library_label
|
||||
)
|
||||
} else {
|
||||
setTitle(this, title)
|
||||
}
|
||||
|
||||
val refresh = arguments?.getBoolean(Constants.INTENT_EXTRA_NAME_REFRESH) ?: false
|
||||
|
||||
artistListModel.getMusicFolders()
|
||||
.observe(
|
||||
viewLifecycleOwner,
|
||||
Observer { changedFolders ->
|
||||
if (changedFolders != null) {
|
||||
viewAdapter.notifyDataSetChanged()
|
||||
selectFolderHeader!!.setData(
|
||||
activeServerProvider.getActiveServer().musicFolderId,
|
||||
changedFolders
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
val artists = artistListModel.getArtists(refresh, refreshArtistListView!!)
|
||||
artists.observe(
|
||||
viewLifecycleOwner, Observer { changedArtists -> viewAdapter.setData(changedArtists) }
|
||||
)
|
||||
|
||||
viewManager = LinearLayoutManager(this.context)
|
||||
viewAdapter = ArtistRowAdapter(
|
||||
artists.value ?: listOf(),
|
||||
selectFolderHeader,
|
||||
{ artist -> onItemClick(artist) },
|
||||
{ menuItem, artist -> onArtistMenuItemSelected(menuItem, artist) },
|
||||
imageLoaderProvider.getImageLoader()
|
||||
)
|
||||
|
||||
artistListView = view.findViewById<RecyclerView>(R.id.select_artist_list).apply {
|
||||
setHasFixedSize(true)
|
||||
layoutManager = viewManager
|
||||
adapter = viewAdapter
|
||||
}
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
}
|
||||
|
||||
private fun onItemClick(artist: Artist) {
|
||||
val bundle = Bundle()
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_ID, artist.id)
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, artist.name)
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_PARENT_ID, artist.id)
|
||||
bundle.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, true)
|
||||
findNavController().navigate(R.id.selectArtistToSelectAlbum, bundle)
|
||||
}
|
||||
|
||||
private fun onArtistMenuItemSelected(menuItem: MenuItem, artist: Artist): Boolean {
|
||||
when (menuItem.itemId) {
|
||||
R.id.artist_menu_play_now ->
|
||||
downloadHandler.downloadRecursively(
|
||||
this,
|
||||
artist.id,
|
||||
save = false,
|
||||
append = false,
|
||||
autoPlay = true,
|
||||
shuffle = false,
|
||||
background = false,
|
||||
playNext = false,
|
||||
unpin = false,
|
||||
isArtist = true
|
||||
)
|
||||
R.id.artist_menu_play_next ->
|
||||
downloadHandler.downloadRecursively(
|
||||
this,
|
||||
artist.id,
|
||||
save = false,
|
||||
append = false,
|
||||
autoPlay = true,
|
||||
shuffle = true,
|
||||
background = false,
|
||||
playNext = true,
|
||||
unpin = false,
|
||||
isArtist = true
|
||||
)
|
||||
R.id.artist_menu_play_last ->
|
||||
downloadHandler.downloadRecursively(
|
||||
this,
|
||||
artist.id,
|
||||
save = false,
|
||||
append = true,
|
||||
autoPlay = false,
|
||||
shuffle = false,
|
||||
background = false,
|
||||
playNext = false,
|
||||
unpin = false,
|
||||
isArtist = true
|
||||
)
|
||||
R.id.artist_menu_pin ->
|
||||
downloadHandler.downloadRecursively(
|
||||
this,
|
||||
artist.id,
|
||||
save = true,
|
||||
append = true,
|
||||
autoPlay = false,
|
||||
shuffle = false,
|
||||
background = false,
|
||||
playNext = false,
|
||||
unpin = false,
|
||||
isArtist = true
|
||||
)
|
||||
R.id.artist_menu_unpin ->
|
||||
downloadHandler.downloadRecursively(
|
||||
this,
|
||||
artist.id,
|
||||
save = false,
|
||||
append = false,
|
||||
autoPlay = false,
|
||||
shuffle = false,
|
||||
background = false,
|
||||
playNext = false,
|
||||
unpin = true,
|
||||
isArtist = true
|
||||
)
|
||||
R.id.artist_menu_download ->
|
||||
downloadHandler.downloadRecursively(
|
||||
this,
|
||||
artist.id,
|
||||
save = false,
|
||||
append = false,
|
||||
autoPlay = false,
|
||||
shuffle = false,
|
||||
background = true,
|
||||
playNext = false,
|
||||
unpin = false,
|
||||
isArtist = true
|
||||
)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
@ -223,7 +223,7 @@ class ServerSettingsModel(
|
||||
/**
|
||||
* Checks if there are any missing indexes in the ServerSetting list
|
||||
* For displaying the Server Settings in a ListView, it is mandatory that their indexes
|
||||
* are'nt missing. Ideally the indexes are continuous, but some circumstances (e.g.
|
||||
* aren't missing. Ideally the indexes are continuous, but some circumstances (e.g.
|
||||
* concurrency or migration errors) may get them out of order.
|
||||
* This would make the List Adapter crash, so it is best to prepare and check the list.
|
||||
*/
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SelectAlbumFragment.kt
|
||||
* TrackCollectionFragment.kt
|
||||
* Copyright (C) 2009-2021 Ultrasonic developers
|
||||
*
|
||||
* Distributed under terms of the GNU GPLv3 license.
|
||||
@ -8,6 +8,8 @@
|
||||
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
|
||||
@ -29,17 +31,16 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import java.security.SecureRandom
|
||||
import java.util.Collections
|
||||
import java.util.Random
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.launch
|
||||
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.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.subsonic.DownloadHandler
|
||||
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
|
||||
@ -53,21 +54,19 @@ import org.moire.ultrasonic.util.EntryByDiscAndTrackComparator
|
||||
import org.moire.ultrasonic.util.Util
|
||||
import org.moire.ultrasonic.view.AlbumView
|
||||
import org.moire.ultrasonic.view.EntryAdapter
|
||||
import org.moire.ultrasonic.view.SelectMusicFolderView
|
||||
import org.moire.ultrasonic.view.SongView
|
||||
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
|
||||
* Displays a group of tracks, eg. the songs of an album, of a playlist etc.
|
||||
* TODO: Refactor this fragment and model to extend the GenericListFragment
|
||||
*/
|
||||
@KoinApiExtension
|
||||
class SelectAlbumFragment : Fragment() {
|
||||
class TrackCollectionFragment : Fragment() {
|
||||
|
||||
private var refreshAlbumListView: SwipeRefreshLayout? = null
|
||||
private var albumListView: ListView? = null
|
||||
private var header: View? = null
|
||||
private var selectFolderHeader: SelectMusicFolderView? = null
|
||||
private var albumButtons: View? = null
|
||||
private var emptyView: TextView? = null
|
||||
private var selectButton: ImageView? = null
|
||||
@ -91,10 +90,8 @@ class SelectAlbumFragment : Fragment() {
|
||||
private val imageLoaderProvider: ImageLoaderProvider by inject()
|
||||
private val shareHandler: ShareHandler by inject()
|
||||
private var cancellationToken: CancellationToken? = null
|
||||
private val activeServerProvider: ActiveServerProvider by inject()
|
||||
|
||||
private val model: SelectAlbumModel by viewModels()
|
||||
|
||||
private val model: TrackCollectionModel by viewModels()
|
||||
private val random: Random = SecureRandom()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@ -128,22 +125,8 @@ class SelectAlbumFragment : Fragment() {
|
||||
false
|
||||
)
|
||||
|
||||
selectFolderHeader = SelectMusicFolderView(
|
||||
requireContext(), view as ViewGroup
|
||||
) { selectedFolderId ->
|
||||
if (!isOffline()) {
|
||||
val serverSettingsModel: ServerSettingsModel by viewModel()
|
||||
val currentSetting = activeServerProvider.getActiveServer()
|
||||
currentSetting.musicFolderId = selectedFolderId
|
||||
serverSettingsModel.updateItem(currentSetting)
|
||||
}
|
||||
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, _ ->
|
||||
@ -156,7 +139,7 @@ class SelectAlbumFragment : Fragment() {
|
||||
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,
|
||||
R.id.trackCollectionFragment,
|
||||
bundle
|
||||
)
|
||||
} else if (entry != null && entry.isVideo) {
|
||||
@ -197,7 +180,7 @@ class SelectAlbumFragment : Fragment() {
|
||||
}
|
||||
playNextButton!!.setOnClickListener {
|
||||
downloadHandler.download(
|
||||
this@SelectAlbumFragment, append = true,
|
||||
this@TrackCollectionFragment, append = true,
|
||||
save = false, autoPlay = false, playNext = true, shuffle = false,
|
||||
songs = getSelectedSongs(albumListView)
|
||||
)
|
||||
@ -229,6 +212,13 @@ class SelectAlbumFragment : Fragment() {
|
||||
updateDisplay(false)
|
||||
}
|
||||
|
||||
val handler = CoroutineExceptionHandler { _, exception ->
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
context?.let { CommunicationErrorHandler.handleError(exception, it) }
|
||||
}
|
||||
refreshAlbumListView!!.isRefreshing = false
|
||||
}
|
||||
|
||||
private fun updateDisplay(refresh: Boolean) {
|
||||
val args = requireArguments()
|
||||
val id = args.getString(Constants.INTENT_EXTRA_NAME_ID)
|
||||
@ -242,13 +232,8 @@ class SelectAlbumFragment : Fragment() {
|
||||
val playlistName = args.getString(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME)
|
||||
val shareId = args.getString(Constants.INTENT_EXTRA_NAME_SHARE_ID)
|
||||
val shareName = args.getString(Constants.INTENT_EXTRA_NAME_SHARE_NAME)
|
||||
val albumListType = args.getString(
|
||||
Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE
|
||||
)
|
||||
val genreName = args.getString(Constants.INTENT_EXTRA_NAME_GENRE_NAME)
|
||||
val albumListTitle = args.getInt(
|
||||
Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, 0
|
||||
)
|
||||
|
||||
val getStarredTracks = args.getInt(Constants.INTENT_EXTRA_NAME_STARRED, 0)
|
||||
val getVideos = args.getInt(Constants.INTENT_EXTRA_NAME_VIDEOS, 0)
|
||||
val getRandomTracks = args.getInt(Constants.INTENT_EXTRA_NAME_RANDOM, 0)
|
||||
@ -260,14 +245,14 @@ class SelectAlbumFragment : Fragment() {
|
||||
)
|
||||
|
||||
fun setTitle(name: String?) {
|
||||
setTitle(this@SelectAlbumFragment, name)
|
||||
setTitle(this@TrackCollectionFragment, name)
|
||||
}
|
||||
|
||||
fun setTitle(name: Int) {
|
||||
setTitle(this@SelectAlbumFragment, name)
|
||||
setTitle(this@TrackCollectionFragment, name)
|
||||
}
|
||||
|
||||
model.viewModelScope.launch {
|
||||
model.viewModelScope.launch(handler) {
|
||||
refreshAlbumListView!!.isRefreshing = true
|
||||
|
||||
model.getMusicFolders(refresh)
|
||||
@ -281,9 +266,6 @@ class SelectAlbumFragment : Fragment() {
|
||||
} 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)
|
||||
@ -321,7 +303,7 @@ class SelectAlbumFragment : Fragment() {
|
||||
|
||||
if (entry != null && entry.isDirectory) {
|
||||
val inflater = requireActivity().menuInflater
|
||||
inflater.inflate(R.menu.select_album_context, menu)
|
||||
inflater.inflate(R.menu.generic_context_menu, menu)
|
||||
}
|
||||
|
||||
shareButton = menu.findItem(R.id.menu_item_share)
|
||||
@ -330,7 +312,7 @@ class SelectAlbumFragment : Fragment() {
|
||||
shareButton!!.isVisible = !isOffline()
|
||||
}
|
||||
|
||||
val downloadMenuItem = menu.findItem(R.id.album_menu_download)
|
||||
val downloadMenuItem = menu.findItem(R.id.menu_download)
|
||||
if (downloadMenuItem != null) {
|
||||
downloadMenuItem.isVisible = !isOffline()
|
||||
}
|
||||
@ -346,42 +328,42 @@ class SelectAlbumFragment : Fragment() {
|
||||
val entryId = entry.id
|
||||
|
||||
when (menuItem.itemId) {
|
||||
R.id.album_menu_play_now -> {
|
||||
R.id.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 -> {
|
||||
R.id.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 -> {
|
||||
R.id.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 -> {
|
||||
R.id.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 -> {
|
||||
R.id.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 -> {
|
||||
R.id.menu_download -> {
|
||||
downloadHandler.downloadRecursively(
|
||||
this, entryId, save = false, append = false,
|
||||
autoPlay = false, shuffle = false, background = true,
|
||||
@ -389,6 +371,7 @@ class SelectAlbumFragment : Fragment() {
|
||||
)
|
||||
}
|
||||
R.id.select_album_play_all -> {
|
||||
// TODO: Why is this being handled here?!
|
||||
playAll()
|
||||
}
|
||||
R.id.menu_item_share -> {
|
||||
@ -620,63 +603,6 @@ class SelectAlbumFragment : Fragment() {
|
||||
mediaPlayerController.unpin(songs)
|
||||
}
|
||||
|
||||
private val musicFolderObserver = Observer<List<MusicFolder>> { changedFolders ->
|
||||
if (changedFolders != null) {
|
||||
selectFolderHeader!!.setData(
|
||||
activeServerProvider.getActiveServer().musicFolderId,
|
||||
changedFolders
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val albumListObserver = Observer<MusicDirectory> { musicDirectory ->
|
||||
if (musicDirectory.getChildren().isNotEmpty()) {
|
||||
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 (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 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> { musicDirectory ->
|
||||
|
||||
// Hide more button when results are less than album list size
|
||||
@ -699,7 +625,9 @@ class SelectAlbumFragment : Fragment() {
|
||||
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)
|
||||
|
||||
Navigation.findNavController(requireView())
|
||||
.navigate(R.id.trackCollectionFragment, bundle)
|
||||
}
|
||||
|
||||
updateInterfaceWithEntries(musicDirectory)
|
||||
@ -710,7 +638,7 @@ class SelectAlbumFragment : Fragment() {
|
||||
private fun updateInterfaceWithEntries(musicDirectory: MusicDirectory) {
|
||||
val entries = musicDirectory.getChildren()
|
||||
|
||||
if (model.currentDirectoryIsSortable && Util.getShouldSortByDisc()) {
|
||||
if (model.currentListIsSortable && Util.getShouldSortByDisc()) {
|
||||
Collections.sort(entries, EntryByDiscAndTrackComparator())
|
||||
}
|
||||
|
||||
@ -764,18 +692,15 @@ class SelectAlbumFragment : Fragment() {
|
||||
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
|
||||
R.id.trackCollectionFragment, bundle
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (model.showSelectFolderHeader) {
|
||||
if (albumListView!!.headerViewsCount == 0) {
|
||||
albumListView!!.addHeaderView(selectFolderHeader!!.itemView, null, false)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This code path can be removed when getArtist has been moved to
|
||||
// AlbumListFragment (getArtist returns the albums of an artist)
|
||||
pinButton!!.visibility = View.GONE
|
||||
unpinButton!!.visibility = View.GONE
|
||||
downloadButton!!.visibility = View.GONE
|
||||
@ -829,7 +754,7 @@ class SelectAlbumFragment : Fragment() {
|
||||
)
|
||||
}
|
||||
|
||||
model.currentDirectoryIsSortable = true
|
||||
model.currentListIsSortable = true
|
||||
}
|
||||
|
||||
private fun createHeader(
|
||||
@ -847,7 +772,7 @@ class SelectAlbumFragment : Fragment() {
|
||||
val albumHeader = AlbumHeader.processEntries(context, entries)
|
||||
|
||||
val titleView = header!!.findViewById<View>(R.id.select_album_title) as TextView
|
||||
titleView.text = name ?: getTitle(this@SelectAlbumFragment) // getActionBarSubtitle());
|
||||
titleView.text = name ?: getTitle(this@TrackCollectionFragment) // getActionBarSubtitle());
|
||||
|
||||
// Don't show a header if all entries are videos
|
||||
if (albumHeader.isAllVideo) {
|
@ -1,46 +1,40 @@
|
||||
/*
|
||||
* TrackCollectionModel.kt
|
||||
* Copyright (C) 2009-2021 Ultrasonic developers
|
||||
*
|
||||
* Distributed under terms of the GNU GPLv3 license.
|
||||
*/
|
||||
|
||||
package org.moire.ultrasonic.fragment
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import android.os.Bundle
|
||||
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.MusicService
|
||||
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
|
||||
/*
|
||||
* Model for retrieving different collections of tracks from the API
|
||||
* TODO: Refactor this model to extend the GenericListModel
|
||||
*/
|
||||
@KoinApiExtension
|
||||
class SelectAlbumModel(application: Application) : AndroidViewModel(application), KoinComponent {
|
||||
|
||||
private val context: Context
|
||||
get() = getApplication<Application>().applicationContext
|
||||
|
||||
private val activeServerProvider: ActiveServerProvider by inject()
|
||||
class TrackCollectionModel(application: Application) : GenericListModel(application) {
|
||||
|
||||
private val allSongsId = "-1"
|
||||
|
||||
val musicFolders: MutableLiveData<List<MusicFolder>> = MutableLiveData()
|
||||
val albumList: MutableLiveData<MusicDirectory> = MutableLiveData()
|
||||
val currentDirectory: MutableLiveData<MusicDirectory> = MutableLiveData()
|
||||
val songsForGenre: MutableLiveData<MusicDirectory> = MutableLiveData()
|
||||
|
||||
var currentDirectoryIsSortable = true
|
||||
var showHeader = true
|
||||
var showSelectFolderHeader = false
|
||||
|
||||
suspend fun getMusicFolders(refresh: Boolean) {
|
||||
withContext(Dispatchers.IO) {
|
||||
if (!ActiveServerProvider.isOffline()) {
|
||||
if (!isOffline()) {
|
||||
val musicService = MusicServiceFactory.getMusicService()
|
||||
musicFolders.postValue(musicService.getMusicFolders(refresh))
|
||||
}
|
||||
@ -124,6 +118,10 @@ class SelectAlbumModel(application: Application) : AndroidViewModel(application)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: This method should be moved to AlbumListModel,
|
||||
* since it displays a list of albums by a specified artist.
|
||||
*/
|
||||
suspend fun getArtist(refresh: Boolean, id: String?, name: String?) {
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
@ -164,7 +162,7 @@ class SelectAlbumModel(application: Application) : AndroidViewModel(application)
|
||||
|
||||
val musicDirectory: MusicDirectory
|
||||
|
||||
musicDirectory = if (allSongsId == id) {
|
||||
if (allSongsId == id) {
|
||||
val root = MusicDirectory()
|
||||
|
||||
val songs: MutableCollection<MusicDirectory.Entry> = LinkedList()
|
||||
@ -189,10 +187,11 @@ class SelectAlbumModel(application: Application) : AndroidViewModel(application)
|
||||
root.addChild(song)
|
||||
}
|
||||
}
|
||||
root
|
||||
musicDirectory = root
|
||||
} else {
|
||||
service.getAlbum(id, name, refresh)
|
||||
musicDirectory = service.getAlbum(id, name, refresh)
|
||||
}
|
||||
|
||||
currentDirectory.postValue(musicDirectory)
|
||||
}
|
||||
}
|
||||
@ -237,7 +236,7 @@ class SelectAlbumModel(application: Application) : AndroidViewModel(application)
|
||||
val service = MusicServiceFactory.getMusicService()
|
||||
val musicDirectory = service.getRandomSongs(size)
|
||||
|
||||
currentDirectoryIsSortable = false
|
||||
currentListIsSortable = false
|
||||
currentDirectory.postValue(musicDirectory)
|
||||
}
|
||||
}
|
||||
@ -281,49 +280,18 @@ class SelectAlbumModel(application: Application) : AndroidViewModel(application)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getAlbumList(albumListType: String, size: Int, offset: Int) {
|
||||
|
||||
showHeader = false
|
||||
showSelectFolderHeader = !ActiveServerProvider.isOffline() &&
|
||||
!Util.getShouldUseId3Tags() && (
|
||||
(albumListType == AlbumListType.SORTED_BY_NAME.toString()) ||
|
||||
(albumListType == AlbumListType.SORTED_BY_ARTIST.toString())
|
||||
)
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
val service = MusicServiceFactory.getMusicService()
|
||||
val musicDirectory: MusicDirectory
|
||||
val musicFolderId = if (showSelectFolderHeader) {
|
||||
activeServerProvider.getActiveServer().musicFolderId
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
if (Util.getShouldUseId3Tags()) {
|
||||
musicDirectory = service.getAlbumList2(
|
||||
albumListType, size,
|
||||
offset, musicFolderId
|
||||
)
|
||||
} else {
|
||||
musicDirectory = service.getAlbumList(
|
||||
albumListType, size,
|
||||
offset, musicFolderId
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
override fun load(
|
||||
isOffline: Boolean,
|
||||
useId3Tags: Boolean,
|
||||
musicService: MusicService,
|
||||
refresh: Boolean,
|
||||
args: Bundle
|
||||
) {
|
||||
// See To_Do at the top
|
||||
}
|
||||
}
|
@ -1,89 +1,87 @@
|
||||
package org.moire.ultrasonic.view
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.PopupMenu
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.domain.MusicFolder
|
||||
|
||||
/**
|
||||
* This little view shows the currently selected Folder (or catalog) on the music server.
|
||||
* When clicked it will drop down a list of all available Folders and allow you to
|
||||
* select one. The intended usage is to supply a filter to lists of artists, albums, etc
|
||||
*/
|
||||
class SelectMusicFolderView(
|
||||
private val context: Context,
|
||||
root: ViewGroup,
|
||||
private val onUpdate: (String?) -> Unit
|
||||
) : RecyclerView.ViewHolder(
|
||||
LayoutInflater.from(context).inflate(
|
||||
R.layout.select_folder_header, root, false
|
||||
)
|
||||
) {
|
||||
private var musicFolders: List<MusicFolder> = mutableListOf<MusicFolder>()
|
||||
private var selectedFolderId: String? = null
|
||||
private val folderName: TextView = itemView.findViewById(R.id.select_folder_name)
|
||||
private val layout: LinearLayout = itemView.findViewById(R.id.select_folder_header)
|
||||
private val MENU_GROUP_MUSIC_FOLDER = 10
|
||||
|
||||
init {
|
||||
folderName.text = context.getString(R.string.select_artist_all_folders)
|
||||
layout.setOnClickListener { onFolderClick() }
|
||||
}
|
||||
|
||||
fun setData(selectedId: String?, folders: List<MusicFolder>) {
|
||||
selectedFolderId = selectedId
|
||||
musicFolders = folders
|
||||
if (selectedFolderId != null) {
|
||||
for ((id, name) in musicFolders) {
|
||||
if (id == selectedFolderId) {
|
||||
folderName.text = name
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
folderName.text = context.getString(R.string.select_artist_all_folders)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onFolderClick() {
|
||||
val popup = PopupMenu(context, layout)
|
||||
|
||||
var menuItem = popup.menu.add(
|
||||
MENU_GROUP_MUSIC_FOLDER, -1, 0, R.string.select_artist_all_folders
|
||||
)
|
||||
if (selectedFolderId == null || selectedFolderId!!.isEmpty()) {
|
||||
menuItem.isChecked = true
|
||||
}
|
||||
musicFolders.forEachIndexed { i, musicFolder ->
|
||||
val (id, name) = musicFolder
|
||||
menuItem = popup.menu.add(MENU_GROUP_MUSIC_FOLDER, i, i + 1, name)
|
||||
if (id == selectedFolderId) {
|
||||
menuItem.isChecked = true
|
||||
}
|
||||
}
|
||||
|
||||
popup.menu.setGroupCheckable(MENU_GROUP_MUSIC_FOLDER, true, true)
|
||||
|
||||
popup.setOnMenuItemClickListener { item -> onFolderMenuItemSelected(item) }
|
||||
popup.show()
|
||||
}
|
||||
|
||||
private fun onFolderMenuItemSelected(menuItem: MenuItem): Boolean {
|
||||
val selectedFolder = if (menuItem.itemId == -1) null else musicFolders[menuItem.itemId]
|
||||
val musicFolderName = selectedFolder?.name
|
||||
?: context.getString(R.string.select_artist_all_folders)
|
||||
selectedFolderId = selectedFolder?.id
|
||||
|
||||
menuItem.isChecked = true
|
||||
folderName.text = musicFolderName
|
||||
onUpdate(selectedFolderId)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
package org.moire.ultrasonic.view
|
||||
|
||||
import android.content.Context
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.PopupMenu
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.domain.MusicFolder
|
||||
|
||||
/**
|
||||
* This little view shows the currently selected Folder (or catalog) on the music server.
|
||||
* When clicked it will drop down a list of all available Folders and allow you to
|
||||
* select one. The intended usage is to supply a filter to lists of artists, albums, etc
|
||||
*/
|
||||
class SelectMusicFolderView(
|
||||
private val context: Context,
|
||||
view: View,
|
||||
private val onUpdate: (String?) -> Unit
|
||||
) : RecyclerView.ViewHolder(view) {
|
||||
private var musicFolders: List<MusicFolder> = mutableListOf()
|
||||
private var selectedFolderId: String? = null
|
||||
private val folderName: TextView = itemView.findViewById(R.id.select_folder_name)
|
||||
private val layout: LinearLayout = itemView.findViewById(R.id.select_folder_header)
|
||||
|
||||
init {
|
||||
folderName.text = context.getString(R.string.select_artist_all_folders)
|
||||
layout.setOnClickListener { onFolderClick() }
|
||||
}
|
||||
|
||||
fun setData(selectedId: String?, folders: List<MusicFolder>) {
|
||||
selectedFolderId = selectedId
|
||||
musicFolders = folders
|
||||
if (selectedFolderId != null) {
|
||||
for ((id, name) in musicFolders) {
|
||||
if (id == selectedFolderId) {
|
||||
folderName.text = name
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
folderName.text = context.getString(R.string.select_artist_all_folders)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onFolderClick() {
|
||||
val popup = PopupMenu(context, layout)
|
||||
|
||||
var menuItem = popup.menu.add(
|
||||
MENU_GROUP_MUSIC_FOLDER, -1, 0, R.string.select_artist_all_folders
|
||||
)
|
||||
if (selectedFolderId == null || selectedFolderId!!.isEmpty()) {
|
||||
menuItem.isChecked = true
|
||||
}
|
||||
musicFolders.forEachIndexed { i, musicFolder ->
|
||||
val (id, name) = musicFolder
|
||||
menuItem = popup.menu.add(MENU_GROUP_MUSIC_FOLDER, i, i + 1, name)
|
||||
if (id == selectedFolderId) {
|
||||
menuItem.isChecked = true
|
||||
}
|
||||
}
|
||||
|
||||
popup.menu.setGroupCheckable(MENU_GROUP_MUSIC_FOLDER, true, true)
|
||||
|
||||
popup.setOnMenuItemClickListener { item -> onFolderMenuItemSelected(item) }
|
||||
popup.show()
|
||||
}
|
||||
|
||||
private fun onFolderMenuItemSelected(menuItem: MenuItem): Boolean {
|
||||
val selectedFolder = if (menuItem.itemId == -1) null else musicFolders[menuItem.itemId]
|
||||
val musicFolderName = selectedFolder?.name
|
||||
?: context.getString(R.string.select_artist_all_folders)
|
||||
selectedFolderId = selectedFolder?.id
|
||||
|
||||
menuItem.isChecked = true
|
||||
folderName.text = musicFolderName
|
||||
onUpdate(selectedFolderId)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MENU_GROUP_MUSIC_FOLDER = 10
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 41 KiB |
@ -1,12 +1,13 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M0 0 L24 0 L24 24 L0 24 Z"/>
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="M12,3v10.55c-0.59,-0.34 -1.27,-0.55 -2,-0.55 -2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4V7h4V3h-6z"/>
|
||||
android:width="200dp"
|
||||
android:height="200dp"
|
||||
android:viewportWidth="100"
|
||||
android:viewportHeight="100">
|
||||
<path
|
||||
android:pathData="M0,0h100v100h-100z"
|
||||
android:fillColor="#1a1a1a"/>
|
||||
<path
|
||||
android:pathData="M42.415,84.787C49.463,84.53 55.166,77.789 54.75,70.704 55.614,58.35 56.479,45.979 57.342,33.633c0.513,-0.518 1.407,0.72 1.903,0.815 5.393,3.785 9.987,9.62 9.845,16.528 -0.003,5.402 -1.991,10.554 -4.162,15.413C71.552,59.26 74.281,48.374 70.666,39.149 68.858,33.816 64.197,30.278 61.795,25.279A26.452,26.452 0,0 1,58.5 14.397c-0.343,-0.816 -1.323,-0.945 -2.094,-0.999l-0.017,-0.001c-2.434,-0.17 -2.216,1.472 -2.331,3.117l-3.274,46.814c0,0 -1.255,-0.207 -2.188,-0.272 -7.098,-0.965 -15.202,3.666 -16.437,11.095 -1.246,5.877 4.638,11.171 10.257,10.635z"
|
||||
android:strokeWidth="0.85"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
||||
|
@ -1,51 +1,95 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:a="http://schemas.android.com/apk/res/android"
|
||||
a:orientation="horizontal"
|
||||
a:layout_width="fill_parent"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:a="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
a:id="@+id/row_artist_layout"
|
||||
a:layout_width="match_parent"
|
||||
a:layout_height="wrap_content"
|
||||
a:minHeight="?android:attr/listPreferredItemHeight">
|
||||
a:background="?android:attr/selectableItemBackground"
|
||||
a:clickable="true"
|
||||
a:focusable="true">
|
||||
|
||||
<ImageView
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
a:id="@+id/album_coverart"
|
||||
a:layout_width="64dp"
|
||||
a:layout_height="64dp"
|
||||
a:layout_gravity="left|center_vertical"
|
||||
a:paddingLeft="3dip" />
|
||||
a:layout_gravity="center_horizontal|center_vertical"
|
||||
a:layout_marginStart="6dp"
|
||||
a:layout_marginLeft="6dp"
|
||||
a:layout_marginTop="6dp"
|
||||
a:scaleType="fitCenter"
|
||||
a:src="@drawable/unknown_album"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearanceOverlay="@style/largeRoundedImageView" />
|
||||
|
||||
<LinearLayout
|
||||
a:id="@+id/row_album_details"
|
||||
a:layout_width="0dp"
|
||||
a:layout_height="74dp"
|
||||
a:layout_marginStart="10dp"
|
||||
a:layout_marginLeft="10dp"
|
||||
a:drawablePadding="6dip"
|
||||
a:gravity="center_vertical"
|
||||
a:minHeight="56dip"
|
||||
a:orientation="vertical"
|
||||
a:layout_width="0dip"
|
||||
a:layout_height="wrap_content"
|
||||
a:layout_weight="1"
|
||||
a:layout_gravity="left|center_vertical"
|
||||
a:paddingLeft="6dip"
|
||||
a:paddingRight="3dip">
|
||||
a:paddingLeft="3dip"
|
||||
a:paddingRight="3dip"
|
||||
a:textAppearance="?android:attr/textAppearanceMedium"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline2"
|
||||
app:layout_constraintLeft_toRightOf="@+id/album_coverart"
|
||||
app:layout_constraintStart_toEndOf="@+id/album_coverart"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
a:id="@+id/album_title"
|
||||
a:layout_width="wrap_content"
|
||||
a:layout_height="wrap_content"
|
||||
a:textAppearance="?android:attr/textAppearanceMedium"
|
||||
a:ellipsize="marquee"
|
||||
a:singleLine="true"
|
||||
a:ellipsize="marquee" />
|
||||
a:textAppearance="?android:attr/textAppearanceMedium"
|
||||
tools:text="TITLE" />
|
||||
|
||||
<TextView
|
||||
a:id="@+id/album_artist"
|
||||
a:layout_width="wrap_content"
|
||||
a:layout_height="wrap_content"
|
||||
a:singleLine="true"
|
||||
a:textAppearance="?android:attr/textAppearanceSmall"
|
||||
a:singleLine="true" />
|
||||
tools:text="ARTIST" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
a:id="@+id/album_star"
|
||||
a:layout_width="38dp"
|
||||
a:layout_height="fill_parent"
|
||||
a:gravity="center_vertical"
|
||||
a:layout_height="38dp"
|
||||
a:layout_marginStart="16dp"
|
||||
a:layout_marginLeft="16dp"
|
||||
a:layout_marginTop="16dp"
|
||||
a:background="@android:color/transparent"
|
||||
a:src="?attr/star_hollow"
|
||||
a:focusable="false"
|
||||
a:paddingRight="3dip" />
|
||||
a:gravity="center_horizontal"
|
||||
a:paddingRight="3dip"
|
||||
a:src="?attr/star_hollow"
|
||||
app:layout_constraintLeft_toRightOf="@+id/row_album_details"
|
||||
app:layout_constraintStart_toEndOf="@+id/row_album_details"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@drawable/ic_star_hollow_dark"
|
||||
a:paddingEnd="3dip" />
|
||||
|
||||
</LinearLayout>
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
a:id="@+id/guideline"
|
||||
a:layout_width="wrap_content"
|
||||
a:layout_height="wrap_content"
|
||||
a:orientation="vertical"
|
||||
app:layout_constraintGuide_begin="76dp" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
a:id="@+id/guideline2"
|
||||
a:layout_width="wrap_content"
|
||||
a:layout_height="wrap_content"
|
||||
a:orientation="vertical"
|
||||
app:layout_constraintGuide_begin="346dp" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
51
ultrasonic/src/main/res/layout/album_list_item_legacy.xml
Normal file
51
ultrasonic/src/main/res/layout/album_list_item_legacy.xml
Normal file
@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:a="http://schemas.android.com/apk/res/android"
|
||||
a:orientation="horizontal"
|
||||
a:layout_width="fill_parent"
|
||||
a:layout_height="wrap_content"
|
||||
a:minHeight="?android:attr/listPreferredItemHeight">
|
||||
|
||||
<ImageView
|
||||
a:id="@+id/album_coverart"
|
||||
a:layout_width="64dp"
|
||||
a:layout_height="64dp"
|
||||
a:layout_gravity="left|center_vertical"
|
||||
a:paddingLeft="3dip" />
|
||||
|
||||
<LinearLayout
|
||||
a:orientation="vertical"
|
||||
a:layout_width="0dip"
|
||||
a:layout_height="wrap_content"
|
||||
a:layout_weight="1"
|
||||
a:layout_gravity="left|center_vertical"
|
||||
a:paddingLeft="6dip"
|
||||
a:paddingRight="3dip">
|
||||
|
||||
<TextView
|
||||
a:id="@+id/album_title"
|
||||
a:layout_width="wrap_content"
|
||||
a:layout_height="wrap_content"
|
||||
a:textAppearance="?android:attr/textAppearanceMedium"
|
||||
a:singleLine="true"
|
||||
a:ellipsize="marquee" />
|
||||
|
||||
<TextView
|
||||
a:id="@+id/album_artist"
|
||||
a:layout_width="wrap_content"
|
||||
a:layout_height="wrap_content"
|
||||
a:textAppearance="?android:attr/textAppearanceSmall"
|
||||
a:singleLine="true" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
a:id="@+id/album_star"
|
||||
a:layout_width="38dp"
|
||||
a:layout_height="fill_parent"
|
||||
a:gravity="center_vertical"
|
||||
a:background="@android:color/transparent"
|
||||
a:src="?attr/star_hollow"
|
||||
a:focusable="false"
|
||||
a:paddingRight="3dip" />
|
||||
|
||||
</LinearLayout>
|
@ -6,13 +6,13 @@
|
||||
a:orientation="vertical">
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
a:id="@+id/select_artist_refresh"
|
||||
a:id="@+id/generic_list_refresh"
|
||||
a:layout_width="fill_parent"
|
||||
a:layout_height="0dip"
|
||||
a:layout_weight="1.0">
|
||||
|
||||
<com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView
|
||||
a:id="@+id/select_artist_list"
|
||||
a:id="@+id/generic_list_recycler"
|
||||
a:layout_width="match_parent"
|
||||
a:layout_height="match_parent"
|
||||
a:paddingTop="8dp"
|
@ -19,8 +19,10 @@
|
||||
android:id="@+id/now_playing_image"
|
||||
android:layout_width="64.0dip"
|
||||
android:layout_height="64.0dip"
|
||||
android:layout_marginLeft="6dp"
|
||||
android:focusable="true"
|
||||
android:gravity="center" />
|
||||
android:gravity="center"
|
||||
android:layout_marginStart="6dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0.0dp"
|
||||
@ -28,7 +30,8 @@
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="1.0"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="11.0dip">
|
||||
android:paddingLeft="11.0dip"
|
||||
android:paddingStart="11.0dip">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/now_playing_trackname"
|
||||
|
@ -2,22 +2,22 @@
|
||||
<menu xmlns:a="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<item
|
||||
a:id="@+id/artist_menu_play_now"
|
||||
a:id="@+id/menu_play_now"
|
||||
a:title="@string/common.play_now"/>
|
||||
<item
|
||||
a:id="@+id/artist_menu_play_next"
|
||||
a:id="@+id/menu_play_next"
|
||||
a:title="@string/common.play_next"/>
|
||||
<item
|
||||
a:id="@+id/artist_menu_play_last"
|
||||
a:id="@+id/menu_play_last"
|
||||
a:title="@string/common.play_last"/>
|
||||
<item
|
||||
a:id="@+id/artist_menu_pin"
|
||||
a:id="@+id/menu_pin"
|
||||
a:title="@string/common.pin"/>
|
||||
<item
|
||||
a:id="@+id/artist_menu_unpin"
|
||||
a:id="@+id/menu_unpin"
|
||||
a:title="@string/common.unpin"/>
|
||||
<item
|
||||
a:id="@+id/artist_menu_download"
|
||||
a:id="@+id/menu_download"
|
||||
a:title="@string/common.download"/>
|
||||
|
||||
</menu>
|
@ -2,22 +2,22 @@
|
||||
<menu xmlns:a="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<item
|
||||
a:id="@+id/album_menu_play_now"
|
||||
a:id="@+id/menu_play_now"
|
||||
a:title="@string/common.play_now"/>
|
||||
<item
|
||||
a:id="@+id/album_menu_play_next"
|
||||
a:title="@string/common.play_next"/>
|
||||
<item
|
||||
a:id="@+id/album_menu_play_last"
|
||||
a:id="@+id/menu_play_last"
|
||||
a:title="@string/common.play_last"/>
|
||||
<item
|
||||
a:id="@+id/album_menu_pin"
|
||||
a:id="@+id/menu_pin"
|
||||
a:title="@string/common.pin"/>
|
||||
<item
|
||||
a:id="@+id/album_menu_unpin"
|
||||
a:id="@+id/menu_unpin"
|
||||
a:title="@string/common.unpin"/>
|
||||
<item
|
||||
a:id="@+id/album_menu_download"
|
||||
a:id="@+id/menu_download"
|
||||
a:title="@string/common.download"/>
|
||||
<item
|
||||
a:id="@+id/menu_item_share"
|
@ -11,7 +11,7 @@
|
||||
a:icon="?attr/home"
|
||||
a:title="@string/button_bar.home" />
|
||||
<item
|
||||
a:id="@+id/selectArtistFragment"
|
||||
a:id="@+id/mediaLibraryFragment"
|
||||
a:checkable="true"
|
||||
a:icon="?attr/browse"
|
||||
a:title="@string/button_bar.browse" />
|
||||
|
@ -8,8 +8,14 @@
|
||||
android:name="org.moire.ultrasonic.fragment.MainFragment"
|
||||
android:label="@string/common.appname" >
|
||||
<action
|
||||
android:id="@+id/mainToSelectAlbum"
|
||||
app:destination="@id/selectAlbumFragment" />
|
||||
android:id="@+id/mainToTrackCollection"
|
||||
app:destination="@id/trackCollectionFragment" />
|
||||
<action
|
||||
android:id="@+id/mainToAlbumList"
|
||||
app:destination="@id/albumListFragment" />
|
||||
<action
|
||||
android:id="@+id/mainToArtistList"
|
||||
app:destination="@id/artistListFragment" />
|
||||
<action
|
||||
android:id="@+id/mainToSelectGenre"
|
||||
app:destination="@id/selectGenreFragment" />
|
||||
@ -18,37 +24,48 @@
|
||||
app:destination="@id/serverSelectorFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/selectArtistFragment"
|
||||
android:name="org.moire.ultrasonic.fragment.SelectArtistFragment"
|
||||
android:id="@+id/mediaLibraryFragment"
|
||||
android:name="org.moire.ultrasonic.fragment.ArtistListFragment"
|
||||
android:label="@string/music_library.label" >
|
||||
<action
|
||||
android:id="@+id/selectArtistToSelectAlbum"
|
||||
app:destination="@id/selectAlbumFragment" />
|
||||
app:destination="@id/trackCollectionFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/selectAlbumFragment"
|
||||
android:name="org.moire.ultrasonic.fragment.SelectAlbumFragment" >
|
||||
android:id="@+id/artistListFragment"
|
||||
android:name="org.moire.ultrasonic.fragment.ArtistListFragment" >
|
||||
<action
|
||||
android:id="@+id/selectArtistToSelectAlbum"
|
||||
app:destination="@id/trackCollectionFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/trackCollectionFragment"
|
||||
android:name="org.moire.ultrasonic.fragment.TrackCollectionFragment" >
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/albumListFragment"
|
||||
android:name="org.moire.ultrasonic.fragment.AlbumListFragment" >
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/searchFragment"
|
||||
android:name="org.moire.ultrasonic.fragment.SearchFragment" >
|
||||
<action
|
||||
android:id="@+id/searchToSelectAlbum"
|
||||
app:destination="@id/selectAlbumFragment" />
|
||||
app:destination="@id/trackCollectionFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/playlistsFragment"
|
||||
android:name="org.moire.ultrasonic.fragment.PlaylistsFragment" >
|
||||
<action
|
||||
android:id="@+id/playlistsToSelectAlbum"
|
||||
app:destination="@id/selectAlbumFragment" />
|
||||
app:destination="@id/trackCollectionFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/sharesFragment"
|
||||
android:name="org.moire.ultrasonic.fragment.SharesFragment" >
|
||||
<action
|
||||
android:id="@+id/sharesToSelectAlbum"
|
||||
app:destination="@id/selectAlbumFragment" />
|
||||
app:destination="@id/trackCollectionFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/bookmarksFragment"
|
||||
@ -61,7 +78,7 @@
|
||||
android:name="org.moire.ultrasonic.fragment.PodcastFragment" >
|
||||
<action
|
||||
android:id="@+id/podcastToSelectAlbum"
|
||||
app:destination="@id/selectAlbumFragment" />
|
||||
app:destination="@id/trackCollectionFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/settingsFragment"
|
||||
@ -81,7 +98,7 @@
|
||||
android:name="org.moire.ultrasonic.fragment.PlayerFragment" >
|
||||
<action
|
||||
android:id="@+id/playerToSelectAlbum"
|
||||
app:destination="@id/selectAlbumFragment" />
|
||||
app:destination="@id/trackCollectionFragment" />
|
||||
<action
|
||||
android:id="@+id/playerToLyrics"
|
||||
app:destination="@id/lyricsFragment" />
|
||||
|
@ -25,6 +25,11 @@
|
||||
<item name="cornerSize">8dp</item>
|
||||
</style>
|
||||
|
||||
<style name="largeRoundedImageView" parent="">
|
||||
<item name="cornerFamily">rounded</item>
|
||||
<item name="cornerSize">2dp</item>
|
||||
</style>
|
||||
|
||||
<style name="ThemeOverlay.AppCompat.navTheme">
|
||||
<item name="colorPrimary">?attr/color_menu_selected</item>
|
||||
<item name="colorControlHighlight">?attr/color_selected</item>
|
||||
|
Loading…
x
Reference in New Issue
Block a user