/* * Copyright (c) 2020 Hemanth Savarla. * * Licensed under the GNU General Public License v3 * * This 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. * * This software 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. * */ package code.name.monkey.retromusic.fragments.albums import android.app.ActivityOptions import android.content.Intent import android.os.Bundle import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.SubMenu import android.view.View import androidx.appcompat.app.AppCompatActivity import androidx.core.os.bundleOf import androidx.core.text.HtmlCompat import androidx.core.view.ViewCompat import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackgroundColor import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.retromusic.EXTRA_ALBUM_ID import code.name.monkey.retromusic.EXTRA_ARTIST_ID import code.name.monkey.retromusic.R import code.name.monkey.retromusic.activities.tageditor.AbsTagEditorActivity import code.name.monkey.retromusic.activities.tageditor.AlbumTagEditorActivity import code.name.monkey.retromusic.adapter.album.HorizontalAlbumAdapter import code.name.monkey.retromusic.adapter.song.SimpleSongAdapter import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog import code.name.monkey.retromusic.dialogs.DeleteSongsDialog import code.name.monkey.retromusic.extensions.applyColor import code.name.monkey.retromusic.extensions.applyOutlineColor import code.name.monkey.retromusic.extensions.findActivityNavController import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment import code.name.monkey.retromusic.glide.AlbumGlideRequest import code.name.monkey.retromusic.glide.ArtistGlideRequest import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.glide.SingleColorTarget import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.SortOrder import code.name.monkey.retromusic.interfaces.IAlbumClickListener import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.network.Result import code.name.monkey.retromusic.network.model.LastFmAlbum import code.name.monkey.retromusic.repository.RealRepository import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import com.bumptech.glide.Glide import com.google.android.material.transition.MaterialContainerTransform import kotlinx.android.synthetic.main.fragment_album_content.* import kotlinx.android.synthetic.main.fragment_album_details.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.koin.android.ext.android.get import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf import java.util.* class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_details), IAlbumClickListener { private val arguments by navArgs() private val detailsViewModel by viewModel { parametersOf(arguments.extraAlbumId) } private lateinit var simpleSongAdapter: SimpleSongAdapter private lateinit var album: Album private val savedSortOrder: String get() = PreferenceUtil.albumDetailSongSortOrder private fun setUpTransitions() { val transform = MaterialContainerTransform() transform.setAllContainerColors(ATHUtil.resolveColor(requireContext(), R.attr.colorSurface)) sharedElementEnterTransition = transform } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setUpTransitions() } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setHasOptionsMenu(true) mainActivity.setBottomBarVisibility(View.GONE) mainActivity.addMusicServiceEventListener(detailsViewModel) mainActivity.setSupportActionBar(toolbar) toolbar.title = " " ViewCompat.setTransitionName(container, "album") postponeEnterTransition() detailsViewModel.getAlbum().observe(viewLifecycleOwner, Observer { startPostponedEnterTransition() showAlbum(it) }) setupRecyclerView() artistImage.setOnClickListener { artistView -> ViewCompat.setTransitionName(artistView, "artist") findActivityNavController(R.id.fragment_container) .navigate( R.id.artistDetailsFragment, bundleOf(EXTRA_ARTIST_ID to album.artistId), null, FragmentNavigatorExtras(artistView to "artist") ) } playAction.setOnClickListener { MusicPlayerRemote.openQueue(album.songs, 0, true) } shuffleAction.setOnClickListener { MusicPlayerRemote.openAndShuffleQueue( album.songs, true ) } aboutAlbumText.setOnClickListener { if (aboutAlbumText.maxLines == 4) { aboutAlbumText.maxLines = Integer.MAX_VALUE } else { aboutAlbumText.maxLines = 4 } } image.apply { transitionName = getString(R.string.transition_album_art) } } override fun onDestroy() { super.onDestroy() serviceActivity?.removeMusicServiceEventListener(detailsViewModel) } private fun setupRecyclerView() { simpleSongAdapter = SimpleSongAdapter( requireActivity() as AppCompatActivity, ArrayList(), R.layout.item_song, null ) recyclerView.apply { layoutManager = LinearLayoutManager(requireContext()) itemAnimator = DefaultItemAnimator() isNestedScrollingEnabled = false adapter = simpleSongAdapter } } private fun showAlbum(album: Album) { if (album.songs.isEmpty()) { return } this.album = album albumTitle.text = album.title val songText = resources.getQuantityString( R.plurals.albumSongs, album.songCount, album.songCount ) songTitle.text = songText if (MusicUtil.getYearString(album.year) == "-") { albumText.text = String.format( "%s • %s", album.artistName, MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(album.songs)) ) } else { albumText.text = String.format( "%s • %s • %s", album.artistName, MusicUtil.getYearString(album.year), MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(album.songs)) ) } loadAlbumCover(album) simpleSongAdapter.swapDataSet(album.songs) detailsViewModel.getArtist(album.artistId).observe(viewLifecycleOwner, Observer { loadArtistImage(it) }) detailsViewModel.getAlbumInfo(album).observe(viewLifecycleOwner, Observer { result -> when (result) { is Result.Loading -> { println("Loading") } is Result.Error -> { println("Error") } is Result.Success -> { aboutAlbum(result.data) } } }) } private fun moreAlbums(albums: List) { moreTitle.show() moreRecyclerView.show() moreTitle.text = String.format(getString(R.string.label_more_from), album.artistName) val albumAdapter = HorizontalAlbumAdapter(requireActivity() as AppCompatActivity, albums, null, this) moreRecyclerView.layoutManager = GridLayoutManager( requireContext(), 1, GridLayoutManager.HORIZONTAL, false ) moreRecyclerView.adapter = albumAdapter } private fun aboutAlbum(lastFmAlbum: LastFmAlbum) { if (lastFmAlbum.album != null) { if (lastFmAlbum.album.wiki != null) { aboutAlbumText.show() aboutAlbumTitle.show() aboutAlbumTitle.text = String.format(getString(R.string.about_album_label), lastFmAlbum.album.name) aboutAlbumText.text = HtmlCompat.fromHtml( lastFmAlbum.album.wiki.content, HtmlCompat.FROM_HTML_MODE_LEGACY ) } if (lastFmAlbum.album.listeners.isNotEmpty()) { listeners.show() listenersLabel.show() scrobbles.show() scrobblesLabel.show() listeners.text = RetroUtil.formatValue(lastFmAlbum.album.listeners.toFloat()) scrobbles.text = RetroUtil.formatValue(lastFmAlbum.album.playcount.toFloat()) } } } private fun loadArtistImage(artist: Artist) { detailsViewModel.getMoreAlbums(artist).observe(viewLifecycleOwner, Observer { moreAlbums(it) }) ArtistGlideRequest.Builder.from(Glide.with(requireContext()), artist) .forceDownload(PreferenceUtil.isAllowedToDownloadMetadata()) .generatePalette(requireContext()) .build() .dontAnimate() .dontTransform() .into(object : RetroMusicColoredTarget(artistImage) { override fun onColorReady(colors: MediaNotificationProcessor) { } }) } private fun loadAlbumCover(album: Album) { AlbumGlideRequest.Builder.from(Glide.with(requireContext()), album.safeGetFirstSong()) .checkIgnoreMediaStore() .generatePalette(requireContext()) .build() .into(object : SingleColorTarget(image) { override fun onColorReady(color: Int) { setColors(color) } }) } private fun setColors(color: Int) { shuffleAction.applyColor(color) playAction.applyOutlineColor(color) } override fun onAlbumClick(albumId: Long, view: View) { findNavController().navigate( R.id.albumDetailsFragment, bundleOf(EXTRA_ALBUM_ID to albumId), null, FragmentNavigatorExtras( view to "album" ) ) } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { super.onCreateOptionsMenu(menu, inflater) inflater.inflate(R.menu.menu_album_detail, menu) val sortOrder = menu.findItem(R.id.action_sort_order) setUpSortOrderMenu(sortOrder.subMenu) ToolbarContentTintHelper.handleOnCreateOptionsMenu( requireContext(), toolbar, menu, getToolbarBackgroundColor(toolbar) ) } override fun onOptionsItemSelected(item: MenuItem): Boolean { return handleSortOrderMenuItem(item) } private fun handleSortOrderMenuItem(item: MenuItem): Boolean { var sortOrder: String? = null val songs = simpleSongAdapter.dataSet when (item.itemId) { android.R.id.home -> findNavController().navigateUp() R.id.action_play_next -> { MusicPlayerRemote.playNext(songs) return true } R.id.action_add_to_current_playing -> { MusicPlayerRemote.enqueue(songs) return true } R.id.action_add_to_playlist -> { lifecycleScope.launch(Dispatchers.IO) { val playlists = get().fetchPlaylists() withContext(Dispatchers.Main) { AddToPlaylistDialog.create(playlists, songs) .show(childFragmentManager, "ADD_PLAYLIST") } } return true } R.id.action_delete_from_device -> { DeleteSongsDialog.create(songs).show(childFragmentManager, "DELETE_SONGS") return true } R.id.action_tag_editor -> { val intent = Intent(requireContext(), AlbumTagEditorActivity::class.java) intent.putExtra(AbsTagEditorActivity.EXTRA_ID, album.id) val options = ActivityOptions.makeSceneTransitionAnimation( requireActivity(), albumCoverContainer, "${getString(R.string.transition_album_art)}_${album.id}" ) startActivityForResult( intent, TAG_EDITOR_REQUEST, options.toBundle() ) return true } /*Sort*/ R.id.action_sort_order_title -> sortOrder = SortOrder.AlbumSongSortOrder.SONG_A_Z R.id.action_sort_order_title_desc -> sortOrder = SortOrder.AlbumSongSortOrder.SONG_Z_A R.id.action_sort_order_track_list -> sortOrder = SortOrder.AlbumSongSortOrder.SONG_TRACK_LIST R.id.action_sort_order_artist_song_duration -> sortOrder = SortOrder.AlbumSongSortOrder.SONG_DURATION } if (sortOrder != null) { item.isChecked = true setSaveSortOrder(sortOrder) } return true } private fun setUpSortOrderMenu(sortOrder: SubMenu) { when (savedSortOrder) { SortOrder.AlbumSongSortOrder.SONG_A_Z -> sortOrder.findItem(R.id.action_sort_order_title) .isChecked = true SortOrder.AlbumSongSortOrder.SONG_Z_A -> sortOrder.findItem(R.id.action_sort_order_title_desc) .isChecked = true SortOrder.AlbumSongSortOrder.SONG_TRACK_LIST -> sortOrder.findItem(R.id.action_sort_order_track_list) .isChecked = true SortOrder.AlbumSongSortOrder.SONG_DURATION -> sortOrder.findItem(R.id.action_sort_order_artist_song_duration) .isChecked = true } } private fun setSaveSortOrder(sortOrder: String) { PreferenceUtil.albumDetailSongSortOrder = sortOrder val songs = when (sortOrder) { SortOrder.AlbumSongSortOrder.SONG_TRACK_LIST -> album.songs.sortedWith { o1, o2 -> o1.trackNumber.compareTo( o2.trackNumber ) } SortOrder.AlbumSongSortOrder.SONG_A_Z -> album.songs.sortedWith { o1, o2 -> o1.title.compareTo( o2.title ) } SortOrder.AlbumSongSortOrder.SONG_Z_A -> album.songs.sortedWith { o1, o2 -> o2.title.compareTo( o1.title ) } SortOrder.AlbumSongSortOrder.SONG_DURATION -> album.songs.sortedWith { o1, o2 -> o1.duration.compareTo( o2.duration ) } else -> throw IllegalArgumentException("invalid $sortOrder") } album = album.copy(songs = songs) simpleSongAdapter.swapDataSet(album.songs) } companion object { const val TAG_EDITOR_REQUEST = 9002 } }