/* * 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.graphics.Color import android.os.Bundle import android.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.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.* 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.MaterialArcMotion 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 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) sharedElementEnterTransition = MaterialContainerTransform().apply { drawingViewId = R.id.fragment_container duration = 300L scrimColor = Color.TRANSPARENT setAllContainerColors(requireContext().resolveColor(R.attr.colorSurface)) setPathMotion(MaterialArcMotion()) } } 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 } }