From bce6dbfa27e445c4f67cb098d112a4ba71c2aba1 Mon Sep 17 00:00:00 2001 From: Prathamesh More Date: Thu, 9 Sep 2021 00:00:20 +0530 Subject: [PATCH] V5 Push Here's a list of changes/features: https://github.com/RetroMusicPlayer/RetroMusicPlayer/releases/tag/v5.0 Internal Changes: 1) Migrated to ViewBinding 2) Migrated to Glide V4 3) Migrated to kotlin version of Material Dialogs --- app/build.gradle | 87 +- app/proguard-rules.pro | 18 +- app/src/debug/res/values/styles.xml | 12 +- app/src/main/AndroidManifest.xml | 43 +- app/src/main/assets/contributors.json | 20 +- app/src/main/assets/retro-changelog.html | 82 +- .../java/code/name/monkey/retromusic/App.kt | 4 +- .../code/name/monkey/retromusic/Constants.kt | 16 +- .../retromusic/LanguageContextWrapper.java | 7 +- .../code/name/monkey/retromusic/MainModule.kt | 20 +- .../retromusic/RetroBottomSheetBehavior.java | 3 + .../activities/DriveModeActivity.kt | 77 +- .../activities/LicenseActivity.java | 14 +- .../activities/LockScreenActivity.kt | 19 +- .../retromusic/activities/LyricsActivity.kt | 316 +++++- .../retromusic/activities/MainActivity.kt | 87 +- .../activities/PermissionActivity.kt | 53 +- .../activities/PlayingQueueActivity.kt | 46 +- .../retromusic/activities/PurchaseActivity.kt | 26 +- .../retromusic/activities/SettingsActivity.kt | 38 +- .../activities/ShareInstagramStory.kt | 45 +- .../activities/SupportDevelopmentActivity.kt | 33 +- .../retromusic/activities/UserInfoActivity.kt | 113 +- .../activities/WhatsNewActivity.java | 111 +- .../activities/base/AbsCastActivity.kt | 141 +++ .../base/AbsMusicServiceActivity.kt | 20 +- .../base/AbsSlidingMusicPanelActivity.kt | 105 +- .../activities/base/AbsThemeActivity.kt | 15 +- .../activities/bugreport/BugReportActivity.kt | 110 +- .../bugreport/model/DeviceInfo.java | 5 +- .../activities/saf/SAFGuideActivity.java | 5 +- .../tageditor/AbsTagEditorActivity.kt | 26 +- .../tageditor/AlbumTagEditorActivity.kt | 90 +- .../tageditor/SongTagEditorActivity.kt | 110 +- .../tageditor/WriteTagsAsyncTask.java | 16 +- .../adapter/CategoryInfoAdapter.java | 8 +- .../monkey/retromusic/adapter/GenreAdapter.kt | 39 +- .../monkey/retromusic/adapter/HomeAdapter.kt | 35 +- .../retromusic/adapter/SearchAdapter.kt | 40 +- .../retromusic/adapter/SongFileAdapter.kt | 13 +- .../retromusic/adapter/StorageAdapter.kt | 55 + .../retromusic/adapter/album/AlbumAdapter.kt | 31 +- .../adapter/album/AlbumCoverPagerAdapter.kt | 11 +- .../adapter/album/HorizontalAlbumAdapter.kt | 12 +- .../adapter/artist/ArtistAdapter.kt | 39 +- .../adapter/base/AbsMultiSelectAdapter.java | 71 +- .../adapter/base/MediaEntryViewHolder.java | 4 +- .../adapter/playlist/PlaylistAdapter.kt | 67 +- .../song/OrderablePlaylistSongAdapter.kt | 193 ++-- .../adapter/song/PlayingQueueAdapter.kt | 9 +- .../adapter/song/ShuffleButtonSongAdapter.kt | 12 + .../retromusic/adapter/song/SongAdapter.kt | 16 +- .../appshortcuts/DynamicShortcutManager.kt | 1 - .../retromusic/appwidgets/AppWidgetBig.kt | 19 +- .../retromusic/appwidgets/AppWidgetCard.kt | 18 +- .../retromusic/appwidgets/AppWidgetClassic.kt | 19 +- .../retromusic/appwidgets/AppWidgetSmall.kt | 19 +- .../retromusic/auto/AutoMediaIDHelper.java | 101 ++ .../retromusic/auto/AutoMusicProvider.kt | 285 ++++++ .../retromusic/auto/MediaItemBuilder.kt | 102 ++ .../name/monkey/retromusic/cast/CastHelper.kt | 83 ++ .../retromusic/cast/CastOptionsProvider.kt | 41 + .../cast/ExpandedControlsActivity.kt | 19 + .../retromusic/cast/RetroSessionManager.kt | 22 + .../monkey/retromusic/cast/RetroWebServer.kt | 140 +++ .../name/monkey/retromusic/db/PlaylistDao.kt | 2 +- .../monkey/retromusic/db/PlaylistEntity.kt | 2 +- .../monkey/retromusic/db/PlaylistWithSongs.kt | 2 +- .../name/monkey/retromusic/db/SongEntity.kt | 2 +- .../dialogs/BlacklistFolderChooserDialog.java | 155 --- .../dialogs/BlacklistFolderChooserDialog.kt | 152 +++ .../dialogs/CreatePlaylistDialog.kt | 27 +- .../retromusic/dialogs/DeleteSongsDialog.kt | 83 +- .../retromusic/dialogs/SleepTimerDialog.kt | 9 +- .../retromusic/dialogs/SongDetailDialog.kt | 4 +- .../monkey/retromusic/extensions/ColorExt.kt | 1 - .../retromusic/extensions/DrawableExt.kt | 4 - .../fragments/DetailListFragment.kt | 70 +- .../retromusic/fragments/LibraryViewModel.kt | 14 +- .../fragments/MiniPlayerFragment.kt | 71 +- .../retromusic/fragments/VolumeFragment.kt | 57 +- .../fragments/about/AboutFragment.kt | 49 +- .../fragments/albums/AlbumDetailsFragment.kt | 224 ++-- .../fragments/albums/AlbumDetailsViewModel.kt | 12 +- .../fragments/albums/AlbumsFragment.kt | 46 +- .../artists/AbsArtistDetailsFragment.kt | 342 +++++++ .../artists/AlbumArtistDetailsFragment.kt | 32 + .../artists/AlbumArtistDetailsViewModel.kt | 66 ++ .../artists/ArtistDetailsFragment.kt | 294 +----- .../artists/ArtistDetailsViewModel.kt | 16 +- .../fragments/artists/ArtistsFragment.kt | 78 +- .../fragments/base/AbsMusicServiceFragment.kt | 10 +- .../base/AbsPlayerControlsFragment.kt | 2 +- .../fragments/base/AbsPlayerFragment.kt | 157 ++- .../AbsRecyclerViewCustomGridSizeFragment.kt | 30 +- .../fragments/base/AbsRecyclerViewFragment.kt | 96 +- .../fragments/folder/FoldersFragment.java | 298 ++++-- .../fragments/genres/GenreDetailsFragment.kt | 51 +- .../fragments/genres/GenreDetailsViewModel.kt | 1 + .../fragments/genres/GenresFragment.kt | 55 +- .../fragments/home/HomeBindingAdapter.kt | 31 + .../retromusic/fragments/home/HomeFragment.kt | 168 +-- .../fragments/library/LibraryFragment.kt | 35 +- .../player/PlayerAlbumCoverFragment.kt | 222 +++- .../player/adaptive/AdaptiveFragment.kt | 18 +- .../AdaptivePlaybackControlsFragment.kt | 84 +- .../blur/BlurPlaybackControlsFragment.kt | 119 ++- .../player/blur/BlurPlayerFragment.kt | 30 +- .../fragments/player/card/CardFragment.kt | 23 +- .../card/CardPlaybackControlsFragment.kt | 105 +- .../player/cardblur/CardBlurFragment.kt | 37 +- .../CardBlurPlaybackControlsFragment.kt | 95 +- .../player/circle/CirclePlayerFragment.kt | 88 +- .../player/classic/ClassicPlayerFragment.kt | 177 ++-- .../fragments/player/color/ColorFragment.kt | 22 +- .../color/ColorPlaybackControlsFragment.kt | 133 ++- .../fragments/player/fit/FitFragment.kt | 17 +- .../player/fit/FitPlaybackControlsFragment.kt | 108 +- .../flat/FlatPlaybackControlsFragment.kt | 97 +- .../player/flat/FlatPlayerFragment.kt | 30 +- .../full/FullPlaybackControlsFragment.kt | 156 +-- .../player/full/FullPlayerFragment.kt | 146 +-- .../player/gradient/GradientPlayerFragment.kt | 229 +++-- .../player/home/HomePlayerFragment.kt | 32 +- .../lockscreen/LockScreenControlsFragment.kt | 88 +- .../material/MaterialControlsFragment.kt | 102 +- .../player/material/MaterialFragment.kt | 18 +- .../fragments/player/normal/PlayerFragment.kt | 27 +- .../normal/PlayerPlaybackControlsFragment.kt | 96 +- .../player/peak/PeakPlayerControlFragment.kt | 72 +- .../player/peak/PeakPlayerFragment.kt | 35 +- .../plain/PlainPlaybackControlsFragment.kt | 100 +- .../player/plain/PlainPlayerFragment.kt | 33 +- .../simple/SimplePlaybackControlsFragment.kt | 99 +- .../player/simple/SimplePlayerFragment.kt | 23 +- .../tiny/TinyPlaybackControlsFragment.kt | 36 +- .../player/tiny/TinyPlayerFragment.kt | 172 +++- .../playlists/PlaylistDetailsFragment.kt | 134 ++- .../playlists/PlaylistDetailsViewModel.kt | 1 + .../fragments/playlists/PlaylistsFragment.kt | 40 +- .../fragments/queue/PlayingQueueFragment.kt | 11 +- .../fragments/search/SearchFragment.kt | 134 ++- .../fragments/settings/AbsSettingsFragment.kt | 4 + .../settings/MainSettingsFragment.kt | 41 +- .../settings/OtherSettingsFragment.kt | 19 + .../settings/ThemeSettingsFragment.kt | 24 +- .../fragments/songs/SongsFragment.kt | 40 +- .../retromusic/glide/AlbumGlideRequest.java | 130 --- .../retromusic/glide/ArtistGlideRequest.java | 171 ---- .../retromusic/glide/BlurTransformation.kt | 98 +- .../glide/ProfileBannerGlideRequest.java | 76 -- .../retromusic/glide/RetroGlideExtension.kt | 192 ++++ .../glide/RetroMusicColoredTarget.kt | 20 +- .../retromusic/glide/RetroMusicGlideModule.kt | 33 +- .../retromusic/glide/SingleColorTarget.kt | 24 +- .../retromusic/glide/SongGlideRequest.java | 144 --- .../glide/UserProfileGlideRequest.java | 83 -- .../glide/artistimage/ArtistImage.kt | 16 + .../glide/artistimage/ArtistImageFetcher.kt | 121 +++ .../glide/artistimage/ArtistImageLoader.kt | 136 +-- .../glide/audiocover/AudioFileCover.java | 15 + .../audiocover/AudioFileCoverFetcher.java | 33 +- .../audiocover/AudioFileCoverLoader.java | 31 +- .../glide/audiocover/AudioFileCoverUtils.java | 16 +- .../glide/palette/BitmapPaletteResource.java | 18 +- .../glide/palette/BitmapPaletteTarget.java | 5 +- .../palette/BitmapPaletteTranscoder.java | 34 +- .../glide/palette/BitmapPaletteWrapper.java | 1 + .../glide/playlistPreview/PlaylistPreview.kt | 31 + .../playlistPreview/PlaylistPreviewFetcher.kt | 51 + .../playlistPreview/PlaylistPreviewLoader.kt | 36 + .../retromusic/helper/MusicPlayerRemote.kt | 29 +- .../retromusic/helper/SearchQueryHelper.kt | 28 +- .../monkey/retromusic/helper/SortOrder.kt | 6 +- .../monkey/retromusic/helper/StackBlur.java | 1 + .../retromusic/helper/menu/SongMenuHelper.kt | 12 + .../interfaces/IAlbumArtistClickListener.kt | 21 + .../interfaces/IMusicServiceEventListener.kt | 2 + .../monkey/retromusic/lyrics/LrcHelper.java | 1 + .../monkey/retromusic/lyrics/LrcUtils.java | 1 + .../monkey/retromusic/lyrics/LrcView.java | 7 +- .../misc/CustomFragmentStatePagerAdapter.java | 2 + .../retromusic/misc/DialogAsyncTask.java | 2 + .../monkey/retromusic/misc/LagTracker.java | 7 +- .../name/monkey/retromusic/model/Album.kt | 4 +- .../name/monkey/retromusic/model/Artist.kt | 41 +- .../monkey/retromusic/model/CategoryInfo.kt | 16 +- .../monkey/retromusic/model/Contributor.kt | 2 +- .../name/monkey/retromusic/model/Genre.kt | 2 +- .../name/monkey/retromusic/model/Playlist.kt | 2 +- .../monkey/retromusic/model/PlaylistSong.kt | 2 +- .../code/name/monkey/retromusic/model/Song.kt | 4 +- .../retromusic/model/lyrics/Lyrics.java | 3 +- .../model/smartplaylist/HistoryPlaylist.kt | 2 +- .../model/smartplaylist/LastAddedPlaylist.kt | 2 +- .../model/smartplaylist/NotPlayedPlaylist.kt | 2 +- .../model/smartplaylist/ShuffleAllPlaylist.kt | 2 +- .../model/smartplaylist/TopTracksPlaylist.kt | 2 +- .../retromusic/network/RetrofitClient.kt | 2 +- .../conversion/LyricsConverterFactory.kt | 7 +- .../retromusic/network/model/LastFmAlbum.java | 1 + .../network/model/LastFmArtist.java | 1 + .../retromusic/network/model/LastFmTrack.java | 1 + .../AlbumCoverStylePreferenceDialog.kt | 2 +- .../preferences/DurationPreference.kt | 95 ++ .../preferences/LibraryPreference.kt | 5 +- .../NowPlayingScreenPreferenceDialog.kt | 2 +- .../retromusic/providers/BlacklistStore.java | 7 +- .../retromusic/providers/HistoryStore.java | 1 + .../providers/MusicPlaybackQueueStore.java | 5 +- .../providers/SongPlayCountStore.java | 1 + .../retromusic/repository/AlbumRepository.kt | 18 +- .../retromusic/repository/ArtistRepository.kt | 59 +- .../retromusic/repository/GenreRepository.kt | 9 +- .../retromusic/repository/Repository.kt | 36 +- .../retromusic/repository/RoomRepository.kt | 13 +- .../retromusic/repository/SearchRepository.kt | 46 +- .../retromusic/repository/SongRepository.kt | 27 +- .../retromusic/repository/SortedCursor.java | 2 + .../repository/SortedLongCursor.java | 2 + .../monkey/retromusic/service/AudioFader.kt | 50 + .../retromusic/service/CrossFadePlayer.kt | 406 ++++++++ .../service/MediaSessionCallback.kt | 79 +- .../retromusic/service/MultiPlayer.java | 514 +++++----- .../retromusic/service/MusicService.java | 195 +++- .../retromusic/service/PlaybackHandler.java | 10 +- .../notification/PlayingNotificationImpl.kt | 19 +- .../notification/PlayingNotificationOreo.kt | 18 +- .../transform/CascadingPageTransformer.kt | 18 +- .../transform/HingeTransformation.kt | 41 +- .../name/monkey/retromusic/util/AppRater.kt | 2 +- .../retromusic/util/ArtistSignatureUtil.java | 12 +- .../util/AutoGeneratedPlaylistBitmap.java | 44 +- .../monkey/retromusic/util/ColorUtil.java | 2 + .../monkey/retromusic/util/Compressor.java | 1 + .../retromusic/util/CustomArtistImageUtil.kt | 33 +- .../monkey/retromusic/util/DensityUtil.kt | 8 +- .../name/monkey/retromusic/util/FileUtil.java | 9 +- .../monkey/retromusic/util/ImageUtil.java | 5 +- .../monkey/retromusic/util/LyricUtil.java | 239 +++-- .../retromusic/util/MergedImageUtils.kt | 2 +- .../name/monkey/retromusic/util/MusicUtil.kt | 49 +- .../retromusic/util/NavigationUtil.java | 5 +- .../retromusic/util/PackageValidator.kt | 347 +++++++ .../monkey/retromusic/util/PlaylistsUtil.java | 11 +- .../monkey/retromusic/util/PreferenceUtil.kt | 59 +- .../retromusic/util/RetroColorUtil.java | 5 +- .../monkey/retromusic/util/RetroUtil.java | 50 +- .../monkey/retromusic/util/RingtoneManager.kt | 1 - .../monkey/retromusic/util/RippleUtils.java | 1 + .../name/monkey/retromusic/util/SAFUtil.java | 14 +- .../retromusic/util/SwipeAndDragHelper.java | 1 + .../name/monkey/retromusic/util/ViewUtil.kt | 5 +- .../color/MediaNotificationProcessor.java | 90 +- .../util/color/NotificationColorUtil.java | 5 +- .../views/BaselineGridTextView.java | 5 +- .../retromusic/views/BreadCrumbLayout.java | 7 +- .../retromusic/views/CircularImageView.java | 2 + .../retromusic/views/ContributorsView.java | 2 + .../monkey/retromusic/views/ListItemView.kt | 23 +- .../retromusic/views/NetworkImageView.java | 5 +- .../monkey/retromusic/views/PermissionItem.kt | 27 +- .../retromusic/views/PopupBackground.java | 2 + ...ollingViewOnApplyWindowInsetsListener.java | 2 + .../name/monkey/retromusic/views/SeekArc.java | 5 +- .../retromusic/views/SettingListItemView.kt | 20 +- .../views/StatusBarMarginFrameLayout.java | 1 + .../retromusic/views/StatusBarView.java | 1 + .../retromusic/views/VerticalTextView.java | 1 + .../volume/AudioVolumeContentObserver.java | 1 + app/src/main/res/drawable/asld_album.xml | 13 + app/src/main/res/drawable/asld_artist.xml | 15 + app/src/main/res/drawable/asld_face.xml | 15 + app/src/main/res/drawable/asld_folder.xml | 13 + app/src/main/res/drawable/asld_guitar.xml | 13 + app/src/main/res/drawable/asld_heart.xml | 18 + app/src/main/res/drawable/asld_music_note.xml | 13 + app/src/main/res/drawable/asld_playlist.xml | 15 + app/src/main/res/drawable/avd_album.xml | 38 + app/src/main/res/drawable/avd_artist.xml | 46 + app/src/main/res/drawable/avd_face.xml | 49 + app/src/main/res/drawable/avd_favorite.xml | 81 ++ app/src/main/res/drawable/avd_folder.xml | 110 ++ app/src/main/res/drawable/avd_guitar.xml | 127 +++ app/src/main/res/drawable/avd_music_note.xml | 114 +++ app/src/main/res/drawable/avd_playlist.xml | 132 +++ app/src/main/res/drawable/avd_unfavorite.xml | 129 +++ app/src/main/res/drawable/ic_album_artist.xml | 16 + app/src/main/res/drawable/ic_baseline.xml | 5 + .../main/res/drawable/ic_favorite_border.xml | 26 +- app/src/main/res/drawable/ic_lyrics.xml | 10 + .../main/res/drawable/ic_lyrics_outline.xml | 20 + .../main/res/drawable/ic_pause_outline.xml | 16 + .../res/drawable/ic_play_arrow_outline.xml | 10 + .../main/res/drawable/ic_playlist_remove.xml | 10 + .../res/drawable/ic_skip_next_outline.xml | 10 + .../res/drawable/ic_skip_previous_outline.xml | 10 + app/src/main/res/drawable/ic_storage.xml | 10 + app/src/main/res/drawable/lyrics_mask.xml | 9 + .../main/res/drawable/shadow_blur_theme.xml | 8 + .../res/drawable/tab_lyrics_indicator.xml | 10 + .../res/layout-land/activity_drive_mode.xml | 2 +- .../layout-land/fragment_album_details.xml | 5 +- .../layout-land/fragment_artist_details.xml | 5 +- .../res/layout-land/fragment_banner_home.xml | 190 ++-- .../main/res/layout-land/fragment_blur.xml | 4 +- .../layout-land/fragment_card_blur_player.xml | 4 +- .../res/layout-land/fragment_card_player.xml | 4 +- .../res/layout-land/fragment_color_player.xml | 6 +- .../res/layout-land/fragment_flat_player.xml | 4 +- .../main/res/layout-land/fragment_home.xml | 6 +- .../res/layout-land/fragment_material.xml | 4 +- .../res/layout-land/fragment_plain_player.xml | 9 +- .../main/res/layout-land/fragment_player.xml | 4 +- .../layout-land/fragment_simple_player.xml | 4 +- app/src/main/res/layout-sw600dp/item_list.xml | 2 +- .../main/res/layout-sw600dp/item_list_big.xml | 123 +++ .../res/layout-xlarge-land/fragment_blur.xml | 4 +- .../layout-xlarge/fragment_about_content.xml | 16 +- app/src/main/res/layout/abs_playlists.xml | 3 + .../main/res/layout/activity_bug_report.xml | 2 + .../main/res/layout/activity_drive_mode.xml | 2 +- .../main/res/layout/activity_lock_screen.xml | 2 +- app/src/main/res/layout/activity_lyrics.xml | 47 +- app/src/main/res/layout/activity_settings.xml | 25 +- .../res/layout/activity_song_tag_editor.xml | 6 +- .../main/res/layout/activity_user_info.xml | 3 +- .../main/res/layout/activity_whats_new.xml | 12 +- app/src/main/res/layout/card_other.xml | 1 - app/src/main/res/layout/card_social.xml | 15 +- .../res/layout/cast_controller_layout.xml | 6 + .../main/res/layout/cast_mini_controller.xml | 107 ++ .../main/res/layout/dialog_edit_lyrics.xml | 39 + app/src/main/res/layout/fragment_about.xml | 4 +- .../res/layout/fragment_about_content.xml | 16 +- .../res/layout/fragment_adaptive_player.xml | 4 +- .../res/layout/fragment_album_content.xml | 12 +- .../res/layout/fragment_album_details.xml | 1 + .../res/layout/fragment_artist_content.xml | 12 +- .../res/layout/fragment_artist_details.xml | 1 + .../main/res/layout/fragment_banner_home.xml | 8 +- app/src/main/res/layout/fragment_blur.xml | 6 +- ...fragment_blur_player_playback_controls.xml | 65 +- .../res/layout/fragment_card_blur_player.xml | 4 +- ...ent_card_blur_player_playback_controls.xml | 4 +- .../main/res/layout/fragment_card_player.xml | 4 +- ...fragment_card_player_playback_controls.xml | 9 +- .../res/layout/fragment_classic_player.xml | 7 +- .../main/res/layout/fragment_color_player.xml | 4 +- ...ragment_color_player_playback_controls.xml | 7 +- app/src/main/res/layout/fragment_fit.xml | 4 +- .../layout/fragment_fit_playback_controls.xml | 5 +- .../main/res/layout/fragment_flat_player.xml | 4 +- ...fragment_flat_player_playback_controls.xml | 5 +- app/src/main/res/layout/fragment_folder.xml | 4 +- app/src/main/res/layout/fragment_full.xml | 4 +- .../layout/fragment_full_player_controls.xml | 5 + .../res/layout/fragment_gradient_controls.xml | 5 + .../res/layout/fragment_gradient_player.xml | 7 +- app/src/main/res/layout/fragment_home.xml | 5 +- .../main/res/layout/fragment_home_player.xml | 2 +- .../res/layout/fragment_main_recycler.xml | 4 +- .../res/layout/fragment_main_settings.xml | 3 +- app/src/main/res/layout/fragment_material.xml | 4 +- .../fragment_material_playback_controls.xml | 52 +- .../main/res/layout/fragment_mini_player.xml | 3 +- .../res/layout/fragment_normal_lyrics.xml | 28 + .../main/res/layout/fragment_peak_player.xml | 23 +- .../main/res/layout/fragment_plain_player.xml | 13 +- app/src/main/res/layout/fragment_player.xml | 4 +- .../layout/fragment_player_album_cover.xml | 45 + .../fragment_player_playback_controls.xml | 21 +- .../res/layout/fragment_playlist_detail.xml | 4 +- app/src/main/res/layout/fragment_search.xml | 127 ++- .../fragment_simple_controls_fragment.xml | 38 +- .../res/layout/fragment_simple_player.xml | 4 +- .../res/layout/fragment_synced_lyrics.xml | 18 + .../main/res/layout/fragment_tiny_player.xml | 4 +- app/src/main/res/layout/home_content.xml | 4 +- app/src/main/res/layout/item_album_card.xml | 46 +- app/src/main/res/layout/item_artist.xml | 8 +- .../main/res/layout/item_artist_square.xml | 2 +- .../main/res/layout/item_favourite_card.xml | 47 + app/src/main/res/layout/item_genre.xml | 75 ++ app/src/main/res/layout/item_grid.xml | 3 +- app/src/main/res/layout/item_image.xml | 1 + app/src/main/res/layout/item_list.xml | 2 +- app/src/main/res/layout/item_list_big.xml | 95 ++ .../res/layout/item_list_quick_actions.xml | 12 +- app/src/main/res/layout/item_permission.xml | 11 + app/src/main/res/layout/item_queue.xml | 4 +- app/src/main/res/layout/item_storage.xml | 53 + .../layout/preference_dialog_audio_fade.xml | 42 + .../res/layout/sliding_music_panel_layout.xml | 8 +- .../main/res/master/values-pt-rPT/strings.xml | 966 +++++++++--------- app/src/main/res/menu/menu_cast.xml | 11 + app/src/main/res/menu/menu_item_directory.xml | 4 + app/src/main/res/menu/menu_item_song.xml | 4 + app/src/main/res/menu/menu_main.xml | 5 + app/src/main/res/menu/menu_player.xml | 23 +- .../menu/menu_playlists_songs_selection.xml | 14 +- app/src/main/res/menu/menu_search.xml | 6 + app/src/main/res/navigation/main_graph.xml | 10 + app/src/main/res/values-my/strings.xml | 665 ++++++++++++ app/src/main/res/values/arrays.xml | 16 +- app/src/main/res/values/dimens.xml | 7 + app/src/main/res/values/ids.xml | 3 + app/src/main/res/values/styles.xml | 19 +- .../res/xml/allowed_media_browser_callers.xml | 251 +++++ app/src/main/res/xml/automotive_app_desc.xml | 4 + app/src/main/res/xml/pref_advanced.xml | 23 +- app/src/main/res/xml/pref_audio.xml | 16 + .../main/res/xml/pref_now_playing_screen.xml | 7 + app/src/main/res/xml/pref_ui.xml | 35 +- appthemehelper/build.gradle | 14 +- .../name/monkey/appthemehelper/ThemeStore.kt | 38 +- .../util/MaterialDialogsUtil.kt | 25 - .../util/ToolbarContentTintHelper.java | 20 +- build.gradle | 12 +- gradle.properties | 1 - gradle/wrapper/gradle-wrapper.properties | 2 +- 421 files changed, 13285 insertions(+), 5757 deletions(-) create mode 100644 app/src/main/java/code/name/monkey/retromusic/activities/base/AbsCastActivity.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/adapter/StorageAdapter.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/auto/AutoMediaIDHelper.java create mode 100644 app/src/main/java/code/name/monkey/retromusic/auto/AutoMusicProvider.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/auto/MediaItemBuilder.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/cast/CastHelper.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/cast/CastOptionsProvider.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/cast/ExpandedControlsActivity.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/cast/RetroSessionManager.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/cast/RetroWebServer.kt delete mode 100644 app/src/main/java/code/name/monkey/retromusic/dialogs/BlacklistFolderChooserDialog.java create mode 100644 app/src/main/java/code/name/monkey/retromusic/dialogs/BlacklistFolderChooserDialog.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/fragments/artists/AbsArtistDetailsFragment.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/fragments/artists/AlbumArtistDetailsFragment.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/fragments/artists/AlbumArtistDetailsViewModel.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/fragments/home/HomeBindingAdapter.kt delete mode 100644 app/src/main/java/code/name/monkey/retromusic/glide/AlbumGlideRequest.java delete mode 100644 app/src/main/java/code/name/monkey/retromusic/glide/ArtistGlideRequest.java delete mode 100644 app/src/main/java/code/name/monkey/retromusic/glide/ProfileBannerGlideRequest.java create mode 100644 app/src/main/java/code/name/monkey/retromusic/glide/RetroGlideExtension.kt delete mode 100644 app/src/main/java/code/name/monkey/retromusic/glide/SongGlideRequest.java delete mode 100644 app/src/main/java/code/name/monkey/retromusic/glide/UserProfileGlideRequest.java create mode 100644 app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImage.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageFetcher.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/glide/playlistPreview/PlaylistPreview.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/glide/playlistPreview/PlaylistPreviewFetcher.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/glide/playlistPreview/PlaylistPreviewLoader.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/interfaces/IAlbumArtistClickListener.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/preferences/DurationPreference.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/service/AudioFader.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/service/CrossFadePlayer.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/util/PackageValidator.kt create mode 100644 app/src/main/res/drawable/asld_album.xml create mode 100644 app/src/main/res/drawable/asld_artist.xml create mode 100644 app/src/main/res/drawable/asld_face.xml create mode 100644 app/src/main/res/drawable/asld_folder.xml create mode 100644 app/src/main/res/drawable/asld_guitar.xml create mode 100644 app/src/main/res/drawable/asld_heart.xml create mode 100644 app/src/main/res/drawable/asld_music_note.xml create mode 100644 app/src/main/res/drawable/asld_playlist.xml create mode 100644 app/src/main/res/drawable/avd_album.xml create mode 100644 app/src/main/res/drawable/avd_artist.xml create mode 100644 app/src/main/res/drawable/avd_face.xml create mode 100644 app/src/main/res/drawable/avd_favorite.xml create mode 100644 app/src/main/res/drawable/avd_folder.xml create mode 100644 app/src/main/res/drawable/avd_guitar.xml create mode 100644 app/src/main/res/drawable/avd_music_note.xml create mode 100644 app/src/main/res/drawable/avd_playlist.xml create mode 100644 app/src/main/res/drawable/avd_unfavorite.xml create mode 100644 app/src/main/res/drawable/ic_album_artist.xml create mode 100644 app/src/main/res/drawable/ic_baseline.xml create mode 100644 app/src/main/res/drawable/ic_lyrics.xml create mode 100644 app/src/main/res/drawable/ic_lyrics_outline.xml create mode 100644 app/src/main/res/drawable/ic_pause_outline.xml create mode 100644 app/src/main/res/drawable/ic_play_arrow_outline.xml create mode 100644 app/src/main/res/drawable/ic_playlist_remove.xml create mode 100644 app/src/main/res/drawable/ic_skip_next_outline.xml create mode 100644 app/src/main/res/drawable/ic_skip_previous_outline.xml create mode 100644 app/src/main/res/drawable/ic_storage.xml create mode 100644 app/src/main/res/drawable/lyrics_mask.xml create mode 100644 app/src/main/res/drawable/shadow_blur_theme.xml create mode 100644 app/src/main/res/drawable/tab_lyrics_indicator.xml create mode 100644 app/src/main/res/layout-sw600dp/item_list_big.xml create mode 100644 app/src/main/res/layout/cast_controller_layout.xml create mode 100644 app/src/main/res/layout/cast_mini_controller.xml create mode 100644 app/src/main/res/layout/dialog_edit_lyrics.xml create mode 100644 app/src/main/res/layout/fragment_normal_lyrics.xml create mode 100644 app/src/main/res/layout/fragment_synced_lyrics.xml create mode 100644 app/src/main/res/layout/item_favourite_card.xml create mode 100644 app/src/main/res/layout/item_genre.xml create mode 100644 app/src/main/res/layout/item_list_big.xml create mode 100644 app/src/main/res/layout/item_storage.xml create mode 100644 app/src/main/res/layout/preference_dialog_audio_fade.xml create mode 100644 app/src/main/res/menu/menu_cast.xml create mode 100644 app/src/main/res/values-my/strings.xml create mode 100644 app/src/main/res/xml/allowed_media_browser_callers.xml create mode 100644 app/src/main/res/xml/automotive_app_desc.xml delete mode 100644 appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/MaterialDialogsUtil.kt diff --git a/app/build.gradle b/app/build.gradle index a13a1350..f504eb7f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,31 +1,29 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' apply plugin: "androidx.navigation.safeargs.kotlin" +apply plugin: 'kotlin-parcelize' android { - compileSdkVersion 29 + compileSdkVersion 31 buildToolsVersion = '29.0.3' defaultConfig { minSdkVersion 21 targetSdkVersion 29 - renderscriptTargetApi 29 //must match target sdk and build tools + renderscriptTargetApi 29//must match target sdk and build tools vectorDrawables.useSupportLibrary = true applicationId "code.name.monkey.retromusic" - versionCode 10503 - versionName '4.0.010' + "_" + getDate() - - multiDexEnabled true + versionCode 10519 + versionName '5.0.0' + "_" + getDate() buildConfigField("String", "GOOGLE_PLAY_LICENSING_KEY", "\"${getProperty(getProperties('../public.properties'), 'GOOGLE_PLAY_LICENSE_KEY')}\"") } signingConfigs { release { - Properties properties = getProperties('/Users/apple/Documents/Github/music.jks') + Properties properties = getProperties('retro.properties') storeFile file(getProperty(properties, 'storeFile')) keyAlias getProperty(properties, 'keyAlias') storePassword getProperty(properties, 'storePassword') @@ -36,7 +34,6 @@ android { release { //debuggable true minifyEnabled true - //shrinkResources true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release } @@ -46,6 +43,10 @@ android { } } + buildFeatures{ + viewBinding true + } + packagingOptions { exclude 'META-INF/LICENSE' exclude 'META-INF/NOTICE' @@ -67,17 +68,11 @@ android { configurations.all { resolutionStrategy.force 'com.google.code.findbugs:jsr305:1.3.9' } - androidExtensions { - experimental = true - } - kapt { - generateStubs = true - } } def getProperties(String fileName) { final Properties properties = new Properties() - def file = file(fileName) + def file = rootProject.file(fileName) if (file.exists()) { file.withInputStream { stream -> properties.load(stream) } } @@ -95,76 +90,76 @@ static def getDate() { dependencies { implementation project(':appthemehelper') - implementation 'androidx.multidex:multidex:2.0.1' implementation "androidx.gridlayout:gridlayout:1.0.0" implementation "androidx.cardview:cardview:1.0.0" - implementation 'androidx.appcompat:appcompat:1.2.0' - implementation 'androidx.annotation:annotation:1.1.0' - implementation 'androidx.constraintlayout:constraintlayout:2.0.4' - implementation 'androidx.recyclerview:recyclerview:1.1.0' + implementation 'androidx.appcompat:appcompat:1.3.1' + implementation 'androidx.annotation:annotation:1.2.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.0' + implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation 'androidx.preference:preference-ktx:1.1.1' - implementation 'androidx.core:core-ktx:1.3.2' - implementation 'androidx.fragment:fragment-ktx:1.2.5' + implementation 'androidx.core:core-ktx:1.6.0' implementation 'androidx.palette:palette-ktx:1.0.0' - def nav_version = "2.3.2" + //Cast Dependencies + implementation 'androidx.mediarouter:mediarouter:1.2.5' + implementation 'com.google.android.gms:play-services-cast-framework:20.0.0' + //WebServer by NanoHttpd + implementation "org.nanohttpd:nanohttpd:2.3.1" + + def nav_version = "2.4.0-alpha07" implementation "androidx.navigation:navigation-runtime-ktx:$nav_version" implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" implementation "androidx.navigation:navigation-ui-ktx:$nav_version" - def room_version = "2.2.5" + def room_version = "2.3.0" implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-ktx:$room_version" kapt "androidx.room:room-compiler:$room_version" - def lifecycle_version = "2.2.0" + def lifecycle_version = "2.3.1" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" implementation 'com.google.android.play:core-ktx:1.8.1' - implementation 'com.google.android.material:material:1.3.0-alpha04' + implementation 'com.google.android.material:material:1.5.0-alpha03' def retrofit_version = '2.9.0' implementation "com.squareup.retrofit2:retrofit:$retrofit_version" implementation "com.squareup.retrofit2:converter-gson:$retrofit_version" - implementation 'com.squareup.okhttp3:logging-interceptor:3.6.0' + implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.2' - def material_dialog_version = "0.9.6.0" + def material_dialog_version = "3.3.0" implementation "com.afollestad.material-dialogs:core:$material_dialog_version" - implementation "com.afollestad.material-dialogs:commons:$material_dialog_version" + implementation "com.afollestad.material-dialogs:input:$material_dialog_version" + implementation "com.afollestad.material-dialogs:color:$material_dialog_version" + implementation "com.afollestad.material-dialogs:bottomsheets:$material_dialog_version" + //noinspection GradleDependency implementation 'com.afollestad:material-cab:0.1.12' - def kotlin_coroutines_version = "1.3.8" - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + + def kotlin_coroutines_version = "1.5.1" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" def koin_version = "2.1.5" implementation "org.koin:koin-core:$koin_version" - implementation "org.koin:koin-core-ext:$koin_version" - implementation "org.koin:koin-androidx-scope:$koin_version" implementation "org.koin:koin-androidx-viewmodel:$koin_version" - implementation "org.koin:koin-androidx-fragment:$koin_version" - implementation "org.koin:koin-androidx-ext:$koin_version" - implementation 'com.github.bumptech.glide:glide:3.8.0' - implementation 'com.github.bumptech.glide:okhttp3-integration:1.5.0' + implementation 'com.github.bumptech.glide:glide:4.12.0' + kapt 'com.github.bumptech.glide:compiler:4.12.0' + implementation 'com.github.bumptech.glide:okhttp3-integration:4.12.0' implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0' implementation 'org.eclipse.mylyn.github:org.eclipse.egit.github.core:3.4.0.201406110918-r' - implementation 'com.github.ksoichiro:android-observablescrollview:1.6.0' - implementation 'com.github.kabouzeid:recyclerview-fastscroll:1.9-kmod' - implementation 'com.github.AdrienPoupa:jaudiotagger:2.2.3' + implementation 'org.bitbucket.ijabz:jaudiotagger:2.2.5' implementation 'com.anjlab.android.iab.v3:library:1.1.0' implementation 'com.r0adkll:slidableactivity:2.1.0' - implementation 'com.heinrichreimersoftware:material-intro:1.6' + implementation 'com.heinrichreimersoftware:material-intro:2.0.0' implementation 'com.github.dhaval2404:imagepicker:1.7.1' - implementation 'org.jsoup:jsoup:1.11.1' - implementation 'me.zhanghai.android.fastscroll:library:1.1.0' - implementation 'me.jorgecastillo:androidcolorx:0.2.0' - implementation 'org.jsoup:jsoup:1.11.1' + implementation 'me.zhanghai.android.fastscroll:library:1.1.7' debugImplementation 'com.amitshekhar.android:debug-db:1.0.6' } diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index c48afcd1..f5edd6ed 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -23,6 +23,9 @@ # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile +-keepnames class ** +-keepnames class ** { *; } +-keepattributes SourceFile,LineNumberTable -dontwarn java.lang.invoke.* -dontwarn **$$Lambda$* @@ -34,9 +37,10 @@ # Glide -keep public class * implements com.bumptech.glide.module.GlideModule --keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { - **[] $VALUES; - public *; +-keep public class * extends com.bumptech.glide.module.AppGlideModule +-keep public enum com.bumptech.glide.load.ImageHeaderParser$** { + **[] $VALUES; + public *; } # OkHttp @@ -47,8 +51,6 @@ #-dontwarn #-ignorewarnings --dontshrink --dontobfuscate -dontwarn org.jaudiotagger.** -keep class org.jaudiotagger.** { *; } @@ -59,4 +61,8 @@ -keepnames class code.name.monkey.retromusic.model.Home -keep class * extends androidx.fragment.app.Fragment{} -keepnames class * extends android.os.Parcelable --keepnames class * extends java.io.Serializable \ No newline at end of file +-keepnames class * extends java.io.Serializable +-keep class code.name.monkey.retromusic.network.model.** { *; } +-keep class code.name.monkey.retromusic.model.CategoryInfo { *; } +-keep class com.google.android.material.bottomsheet.** { *; } +-keep class code.name.monkey.retromusic.Constants { *; } \ No newline at end of file diff --git a/app/src/debug/res/values/styles.xml b/app/src/debug/res/values/styles.xml index dcee15a6..e984e856 100644 --- a/app/src/debug/res/values/styles.xml +++ b/app/src/debug/res/values/styles.xml @@ -6,6 +6,11 @@ @font/sans + + @@ -91,6 +96,11 @@ + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c8b117ed..16fa048d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,9 +5,10 @@ + - + @@ -29,7 +30,7 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.RetroMusic.FollowSystem" - android:usesCleartextTraffic="false" + android:usesCleartextTraffic="true" tools:ignore="AllowBackup,GoogleAppIndexingWarning" tools:targetApi="m"> + + + + + + + - - - - + + + + + diff --git a/app/src/main/assets/contributors.json b/app/src/main/assets/contributors.json index e085d379..7b97dc02 100644 --- a/app/src/main/assets/contributors.json +++ b/app/src/main/assets/contributors.json @@ -6,14 +6,14 @@ "image": "https://i.imgur.com/AoVs9oj.jpg" }, { - "name": "Lennart Glamann", - "summary": "Play Store Banner & Images", - "link": "https://t.me/FlixbusLennart", - "image": "https://i.imgur.com/Q5Nsx1R.jpg" + "name": "Prathamesh More", + "summary": "Developer", + "link": "https://prathameshmm02.github.io", + "image": "https://i.imgur.com/ZHoOrHx.jpg" }, { "name": "Daksh P. Jain", - "summary": "Support Representative & Moderator", + "summary": "Website & GitHub Maintainer", "link": "https://daksh.eu.org", "image": "https://i.imgur.com/fnYpg65.jpg" }, @@ -23,9 +23,15 @@ "link": "https://t.me/MilindGoel15", "image": "https://i.imgur.com/Bz4De21_d.jpg" }, -{ + { + "name": "Lennart Glamann", + "summary": "Play Store Banner & Images", + "link": "https://t.me/FlixbusLennart", + "image": "https://i.imgur.com/Q5Nsx1R.jpg" + }, + { "name": "Haythem Gataa", - "summary": "App Logo Designer", + "summary": "App Logo & Banners", "link": "https://dribbble.com/haythemgataa", "image": "https://i.imgur.com/g5RuIZq.jpg" } diff --git a/app/src/main/assets/retro-changelog.html b/app/src/main/assets/retro-changelog.html index aa3c01e7..6525c677 100644 --- a/app/src/main/assets/retro-changelog.html +++ b/app/src/main/assets/retro-changelog.html @@ -6,9 +6,11 @@ word-wrap: break-word; } - body { - padding-left: 1rem; - padding-right: 1rem; + div{ + margin: 20px 10px; + padding: 10px; + border-radius: 10px; + box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2); } h2 { @@ -18,10 +20,7 @@ li { font-size: 0.85rem; - padding-top: 0.5rem; - padding-left: 0; - padding-right: 0; - color: rgba(0, 0, 0, 0.8); + padding: 0.5rem 0; } ul { @@ -44,39 +43,62 @@ margin-block-end: 0.5rem; } - h3 span { - border-radius: 0.2rem; - padding-left: 0.5rem; - padding-right: 0.5rem; - padding-top: 0.3rem; - padding-bottom: 0.3rem; + h3 { + margin: 10px 0px; font-size: 1rem; } {style-placeholder} + + + + + + -
April 30, 2020
-

v3.5.110

-Beta version -

What's New

-
    -
  • Changed profile form image to icon
  • -
  • New what's new screen
  • -
  • Added In-App language changer, where you can select language
  • -
-

Improved

-
    -
  • Improved loading of Songs, Albums, Artists, Genres, Playlists
  • -
+

+ Clear the app if it crashes after updating +

+
+ +
September 06, 2021
+

v5.0.0

+

What's New

+
    +
  • Added Chromecast support
  • +
  • Added animated icons
  • +
  • Added cross-fade (experimental)
  • +
  • Added ability to remember the last tab
  • +
  • Added whitelisting songs
  • +
  • Added support for embedded synced lyrics
  • +
  • Added lyrics editor for normal and synced lyrics
  • +
  • Added playlist ordering
  • +
  • Added search filters
  • +
  • Added audio fade
  • +
  • Added Multi-select in album and artist details
  • +
  • Added SD card from folders tab
  • +
  • Added Synced lyrics in all themes
  • +
  • Added Swipe anywhere to change the song
  • +
  • Added album artist
  • +
  • Albums now show album artists instead of artists of the first song
  • +
+

Fixed

+
    +
  • Fixed playlist preview images
  • +
  • Fixed language switching
  • +
+

Improved

+
    +
  • Improved playlists tab
  • +
  • Improved genres tab
  • +
+
-

*If you face any UI related issues you clear app data and cache, if itsnot working try to - uninstall and install - again.

- \ No newline at end of file + diff --git a/app/src/main/java/code/name/monkey/retromusic/App.kt b/app/src/main/java/code/name/monkey/retromusic/App.kt index 4de597d3..7e5202f5 100644 --- a/app/src/main/java/code/name/monkey/retromusic/App.kt +++ b/app/src/main/java/code/name/monkey/retromusic/App.kt @@ -14,8 +14,8 @@ */ package code.name.monkey.retromusic +import android.app.Application import android.widget.Toast -import androidx.multidex.MultiDexApplication import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.util.VersionUtils import code.name.monkey.retromusic.Constants.PRO_VERSION_PRODUCT_ID @@ -25,7 +25,7 @@ import com.anjlab.android.iab.v3.TransactionDetails import org.koin.android.ext.koin.androidContext import org.koin.core.context.startKoin -class App : MultiDexApplication() { +class App : Application() { lateinit var billingProcessor: BillingProcessor diff --git a/app/src/main/java/code/name/monkey/retromusic/Constants.kt b/app/src/main/java/code/name/monkey/retromusic/Constants.kt index ed4b38da..738e5aa8 100644 --- a/app/src/main/java/code/name/monkey/retromusic/Constants.kt +++ b/app/src/main/java/code/name/monkey/retromusic/Constants.kt @@ -21,7 +21,8 @@ object Constants { const val PRO_VERSION_PRODUCT_ID = "pro_version" const val RATE_ON_GOOGLE_PLAY = "https://play.google.com/store/apps/details?id=code.name.monkey.retromusic" - const val TRANSLATE = "https://github.com/RetroMusicPlayer/RetroMusicPlayer" + const val TRANSLATE = "https://crowdin.com/project/retromusicplayer" + const val WEBSITE = "https://retromusic.app" const val GITHUB_PROJECT = "https://github.com/RetroMusicPlayer/RetroMusicPlayer" const val TELEGRAM_CHANGE_LOG = "https://t.me/retromusiclog" const val USER_PROFILE = "profile.jpg" @@ -49,7 +50,7 @@ object Constants { MediaStore.Audio.AudioColumns.ARTIST_ID, // 9 MediaStore.Audio.AudioColumns.ARTIST, // 10 MediaStore.Audio.AudioColumns.COMPOSER, // 11 - "album_artist" // 12 + ALBUM_ARTIST // 12 ) const val NUMBER_OF_TOP_TRACKS = 99 } @@ -66,7 +67,7 @@ const val EXTRA_SONG_INFO = "extra_song_info" const val DESATURATED_COLOR = "desaturated_color" const val BLACK_THEME = "black_theme" const val KEEP_SCREEN_ON = "keep_screen_on" -const val TYPE_HOME_BANNER = "type_home_banner" +const val TOGGLE_HOME_BANNER = "toggle_home_banner" const val NOW_PLAYING_SCREEN_ID = "now_playing_screen_id" const val CAROUSEL_EFFECT = "carousel_effect" const val COLORED_NOTIFICATION = "colored_notification" @@ -129,6 +130,7 @@ const val START_DIRECTORY = "start_directory" const val RECENTLY_PLAYED_CUTOFF = "recently_played_interval" const val LOCK_SCREEN = "lock_screen" const val ALBUM_ARTISTS_ONLY = "album_artists_only" +const val ALBUM_ARTIST = "album_artist" const val ALBUM_DETAIL_SONG_SORT_ORDER = "album_detail_song_sort_order" const val LYRICS_OPTIONS = "lyrics_tab_position" const val CHOOSE_EQUALIZER = "choose_equalizer" @@ -138,3 +140,11 @@ const val SONG_GRID_STYLE = "song_grid_style" const val PAUSE_ON_ZERO_VOLUME = "pause_on_zero_volume" const val FILTER_SONG = "filter_song" const val EXPAND_NOW_PLAYING_PANEL = "expand_now_playing_panel" +const val EXTRA_ARTIST_NAME = "extra_artist_name" +const val TOGGLE_SUGGESTIONS = "toggle_suggestions" +const val AUDIO_FADE_DURATION = "audio_fade_duration" +const val CROSS_FADE_DURATION = "cross_fade_duration" +const val SHOW_LYRICS = "show_lyrics" +const val REMEMBER_LAST_TAB = "remember_last_tab" +const val LAST_USED_TAB = "last_used_tab" +const val WHITELIST_MUSIC = "whitelist_music" diff --git a/app/src/main/java/code/name/monkey/retromusic/LanguageContextWrapper.java b/app/src/main/java/code/name/monkey/retromusic/LanguageContextWrapper.java index cbd9204e..564fa56c 100644 --- a/app/src/main/java/code/name/monkey/retromusic/LanguageContextWrapper.java +++ b/app/src/main/java/code/name/monkey/retromusic/LanguageContextWrapper.java @@ -5,15 +5,20 @@ import android.content.ContextWrapper; import android.content.res.Configuration; import android.content.res.Resources; import android.os.LocaleList; -import code.name.monkey.appthemehelper.util.VersionUtils; + +import com.google.android.gms.common.annotation.KeepName; + import java.util.Locale; +import code.name.monkey.appthemehelper.util.VersionUtils; + public class LanguageContextWrapper extends ContextWrapper { public LanguageContextWrapper(Context base) { super(base); } + @KeepName public static LanguageContextWrapper wrap(Context context, Locale newLocale) { Resources res = context.getResources(); Configuration configuration = res.getConfiguration(); diff --git a/app/src/main/java/code/name/monkey/retromusic/MainModule.kt b/app/src/main/java/code/name/monkey/retromusic/MainModule.kt index c701a47e..bce82a70 100644 --- a/app/src/main/java/code/name/monkey/retromusic/MainModule.kt +++ b/app/src/main/java/code/name/monkey/retromusic/MainModule.kt @@ -3,6 +3,7 @@ package code.name.monkey.retromusic import androidx.room.Room import androidx.room.RoomDatabase import androidx.sqlite.db.SupportSQLiteDatabase +import code.name.monkey.retromusic.auto.AutoMusicProvider import code.name.monkey.retromusic.db.BlackListStoreDao import code.name.monkey.retromusic.db.BlackListStoreEntity import code.name.monkey.retromusic.db.PlaylistWithSongs @@ -85,6 +86,18 @@ private val roomModule = module { RealRoomRepository(get(), get(), get(), get(), get()) } bind RoomRepository::class } +private val autoModule = module { + single { + AutoMusicProvider(androidContext(), + get(), + get(), + get(), + get(), + get(), + get() + ) + } +} private val mainModule = module { single { androidContext().contentResolver @@ -167,10 +180,11 @@ private val viewModules = module { ) } - viewModel { (artistId: Long) -> + viewModel { (artistId: Long?, artistName: String?) -> ArtistDetailsViewModel( get(), - artistId + artistId, + artistName ) } @@ -189,4 +203,4 @@ private val viewModules = module { } } -val appModules = listOf(mainModule, dataModule, viewModules, networkModule, roomModule) \ No newline at end of file +val appModules = listOf(mainModule, dataModule, autoModule, viewModules, networkModule, roomModule) \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/RetroBottomSheetBehavior.java b/app/src/main/java/code/name/monkey/retromusic/RetroBottomSheetBehavior.java index 345cda93..1ad8c572 100644 --- a/app/src/main/java/code/name/monkey/retromusic/RetroBottomSheetBehavior.java +++ b/app/src/main/java/code/name/monkey/retromusic/RetroBottomSheetBehavior.java @@ -4,8 +4,11 @@ import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; + import androidx.coordinatorlayout.widget.CoordinatorLayout; + import com.google.android.material.bottomsheet.BottomSheetBehavior; + import org.jetbrains.annotations.NotNull; public class RetroBottomSheetBehavior extends BottomSheetBehavior { diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/DriveModeActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/DriveModeActivity.kt index 3f5e7fbc..d2bbcbe2 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/DriveModeActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/DriveModeActivity.kt @@ -23,10 +23,12 @@ import android.widget.SeekBar import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.retromusic.R import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity +import code.name.monkey.retromusic.databinding.ActivityDriveModeBinding import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment import code.name.monkey.retromusic.glide.BlurTransformation +import code.name.monkey.retromusic.glide.GlideApp +import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.RetroMusicColoredTarget -import code.name.monkey.retromusic.glide.SongGlideRequest import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper.Callback @@ -35,19 +37,19 @@ import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import com.bumptech.glide.Glide -import kotlinx.android.synthetic.main.activity_drive_mode.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext + /** * Created by hemanths on 2020-02-02. */ class DriveModeActivity : AbsMusicServiceActivity(), Callback { + private lateinit var binding: ActivityDriveModeBinding private var lastPlaybackControlsColor: Int = Color.GRAY private var lastDisabledPlaybackControlsColor: Int = Color.GRAY private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper @@ -55,12 +57,13 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback { override fun onCreate(savedInstanceState: Bundle?) { setDrawUnderStatusBar() super.onCreate(savedInstanceState) - setContentView(R.layout.activity_drive_mode) + binding = ActivityDriveModeBinding.inflate(layoutInflater) + setContentView(binding.root) setUpMusicControllers() progressViewUpdateHelper = MusicProgressViewUpdateHelper(this) lastPlaybackControlsColor = ThemeStore.accentColor(this) - close.setOnClickListener { + binding.close.setOnClickListener { onBackPressed() } } @@ -75,7 +78,7 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback { } private fun setupFavouriteToggle() { - songFavourite.setOnClickListener { + binding.songFavourite.setOnClickListener { MusicUtil.toggleFavorite( this@DriveModeActivity, MusicPlayerRemote.currentSong @@ -88,13 +91,13 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback { val isFavourite = MusicUtil.isFavorite(this@DriveModeActivity, MusicPlayerRemote.currentSong) withContext(Dispatchers.Main) { - songFavourite.setImageResource(if (isFavourite) R.drawable.ic_favorite else R.drawable.ic_favorite_border) + binding.songFavourite.setImageResource(if (isFavourite) R.drawable.ic_favorite else R.drawable.ic_favorite_border) } } } private fun setUpProgressSlider() { - progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { + binding.progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) { MusicPlayerRemote.seekTo(progress) @@ -119,20 +122,20 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback { private fun setUpPrevNext() { - nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } - previousButton.setOnClickListener { MusicPlayerRemote.back() } + binding.nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } + binding.previousButton.setOnClickListener { MusicPlayerRemote.back() } } private fun setUpShuffleButton() { - shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } + binding.shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } } private fun setUpRepeatButton() { - repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } + binding.repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } } private fun setUpPlayPauseFab() { - playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) + binding.playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) } override fun onRepeatModeChanged() { @@ -161,19 +164,19 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback { private fun updatePlayPauseDrawableState() { if (MusicPlayerRemote.isPlaying) { - playPauseButton.setImageResource(R.drawable.ic_pause) + binding.playPauseButton.setImageResource(R.drawable.ic_pause) } else { - playPauseButton.setImageResource(R.drawable.ic_play_arrow) + binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow) } } fun updateShuffleState() { when (MusicPlayerRemote.shuffleMode) { - MusicService.SHUFFLE_MODE_SHUFFLE -> shuffleButton.setColorFilter( + MusicService.SHUFFLE_MODE_SHUFFLE -> binding.shuffleButton.setColorFilter( lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) - else -> shuffleButton.setColorFilter( + else -> binding.shuffleButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) @@ -183,19 +186,25 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback { private fun updateRepeatState() { when (MusicPlayerRemote.repeatMode) { MusicService.REPEAT_MODE_NONE -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter( + binding.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.repeatButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) } MusicService.REPEAT_MODE_ALL -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } MusicService.REPEAT_MODE_THIS -> { - repeatButton.setImageResource(R.drawable.ic_repeat_one) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat_one) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } } } @@ -209,29 +218,29 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback { private fun updateSong() { val song = MusicPlayerRemote.currentSong - songTitle.text = song.title - songText.text = song.artistName + binding.songTitle.text = song.title + binding.songText.text = song.artistName - SongGlideRequest.Builder.from(Glide.with(this), song) - .checkIgnoreMediaStore(this) - .generatePalette(this) - .build() + GlideApp.with(this) + .asBitmapPalette() + .songCoverOptions(song) + .load(RetroGlideExtension.getSongModel(song)) .transform(BlurTransformation.Builder(this).build()) - .into(object : RetroMusicColoredTarget(image) { + .into(object : RetroMusicColoredTarget(binding.image) { override fun onColorReady(colors: MediaNotificationProcessor) { } }) } override fun onUpdateProgressViews(progress: Int, total: Int) { - progressSlider.max = total + binding.progressSlider.max = total - val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) + val animator = ObjectAnimator.ofInt(binding.progressSlider, "progress", progress) animator.duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME animator.interpolator = LinearInterpolator() animator.start() - songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) - songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) + binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) } } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/LicenseActivity.java b/app/src/main/java/code/name/monkey/retromusic/activities/LicenseActivity.java index e66b7f81..f9ada218 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/LicenseActivity.java +++ b/app/src/main/java/code/name/monkey/retromusic/activities/LicenseActivity.java @@ -18,19 +18,23 @@ import android.graphics.Color; import android.os.Bundle; import android.view.MenuItem; import android.webkit.WebView; + import androidx.annotation.NonNull; import androidx.appcompat.widget.Toolbar; + +import org.jetbrains.annotations.Nullable; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + import code.name.monkey.appthemehelper.ThemeStore; import code.name.monkey.appthemehelper.util.ATHUtil; import code.name.monkey.appthemehelper.util.ColorUtil; import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; import code.name.monkey.retromusic.R; import code.name.monkey.retromusic.activities.base.AbsBaseActivity; -import java.io.BufferedReader; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import org.jetbrains.annotations.Nullable; /** Created by hemanths on 2019-09-27. */ public class LicenseActivity extends AbsBaseActivity { diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/LockScreenActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/LockScreenActivity.kt index ad3fa148..792c8365 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/LockScreenActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/LockScreenActivity.kt @@ -23,27 +23,29 @@ import android.view.WindowManager import androidx.core.view.ViewCompat import code.name.monkey.retromusic.R import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity +import code.name.monkey.retromusic.databinding.ActivityLockScreenBinding import code.name.monkey.retromusic.extensions.whichFragment import code.name.monkey.retromusic.fragments.player.lockscreen.LockScreenControlsFragment +import code.name.monkey.retromusic.glide.GlideApp +import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.RetroMusicColoredTarget -import code.name.monkey.retromusic.glide.SongGlideRequest import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import com.bumptech.glide.Glide import com.r0adkll.slidr.Slidr import com.r0adkll.slidr.model.SlidrConfig import com.r0adkll.slidr.model.SlidrListener import com.r0adkll.slidr.model.SlidrPosition -import kotlinx.android.synthetic.main.activity_lock_screen.* class LockScreenActivity : AbsMusicServiceActivity() { + private lateinit var binding: ActivityLockScreenBinding private var fragment: LockScreenControlsFragment? = null override fun onCreate(savedInstanceState: Bundle?) { setDrawUnderStatusBar() super.onCreate(savedInstanceState) lockScreenInit() - setContentView(R.layout.activity_lock_screen) + binding = ActivityLockScreenBinding.inflate(layoutInflater) + setContentView(binding.root) hideStatusBar() setStatusbarColorAuto() setNavigationbarColorAuto() @@ -107,9 +109,12 @@ class LockScreenActivity : AbsMusicServiceActivity() { private fun updateSongs() { val song = MusicPlayerRemote.currentSong - SongGlideRequest.Builder.from(Glide.with(this), song).checkIgnoreMediaStore(this) - .generatePalette(this).build().dontAnimate() - .into(object : RetroMusicColoredTarget(image) { + GlideApp.with(this) + .asBitmapPalette() + .songCoverOptions(song) + .load(RetroGlideExtension.getSongModel(song)) + .dontAnimate() + .into(object : RetroMusicColoredTarget(binding.image) { override fun onColorReady(colors: MediaNotificationProcessor) { fragment?.setColor(colors) } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/LyricsActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/LyricsActivity.kt index ac434622..5b72c24a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/LyricsActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/LyricsActivity.kt @@ -15,38 +15,64 @@ package code.name.monkey.retromusic.activities import android.os.Bundle -import android.view.Menu -import android.view.MenuItem -import android.view.WindowManager +import android.text.InputType +import android.view.* import androidx.core.view.ViewCompat -import androidx.interpolator.view.animation.FastOutSlowInInterpolator +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.viewpager2.adapter.FragmentStateAdapter import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.retromusic.R import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity +import code.name.monkey.retromusic.activities.tageditor.WriteTagsAsyncTask +import code.name.monkey.retromusic.databinding.ActivityLyricsBinding +import code.name.monkey.retromusic.databinding.FragmentNormalLyricsBinding +import code.name.monkey.retromusic.databinding.FragmentSyncedLyricsBinding +import code.name.monkey.retromusic.extensions.accentColor import code.name.monkey.retromusic.extensions.surfaceColor +import code.name.monkey.retromusic.extensions.textColorSecondary +import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.lyrics.LrcView +import code.name.monkey.retromusic.model.LoadingInfo import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.LyricUtil import code.name.monkey.retromusic.util.RetroUtil +import com.afollestad.materialdialogs.LayoutMode +import com.afollestad.materialdialogs.MaterialDialog +import com.afollestad.materialdialogs.bottomsheets.BottomSheet +import com.afollestad.materialdialogs.input.input import com.google.android.material.color.MaterialColors -import com.google.android.material.transition.platform.MaterialArcMotion +import com.google.android.material.tabs.TabLayoutMediator import com.google.android.material.transition.platform.MaterialContainerTransform -import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback -import kotlinx.android.synthetic.main.activity_lyrics.* +import kotlinx.coroutines.* +import org.jaudiotagger.audio.AudioFileIO +import org.jaudiotagger.tag.FieldKey +import java.io.File +import java.util.* -class LyricsActivity : AbsMusicServiceActivity(), MusicProgressViewUpdateHelper.Callback { - private lateinit var updateHelper: MusicProgressViewUpdateHelper +class LyricsActivity : AbsMusicServiceActivity() { + private lateinit var binding: ActivityLyricsBinding private lateinit var song: Song + private val lyricsSectionsAdapter = LyricsSectionsAdapter(this) + private val googleSearchLrcUrl: String get() { var baseUrl = "http://www.google.com/search?" var query = song.title + "+" + song.artistName - query = "q=" + query.replace(" ", "+") + " .lrc" + query = "q=" + query.replace(" ", "+") + " lyrics" + baseUrl += query + return baseUrl + } + private val syairSearchLrcUrl: String + get() { + var baseUrl = "https://www.syair.info/search?" + var query = song.title + "+" + song.artistName + query = "q=" + query.replace(" ", "+") baseUrl += query return baseUrl } @@ -63,75 +89,55 @@ class LyricsActivity : AbsMusicServiceActivity(), MusicProgressViewUpdateHelper. override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_lyrics) - ViewCompat.setTransitionName(container, "lyrics") + binding = ActivityLyricsBinding.inflate(layoutInflater) + setContentView(binding.root) + ViewCompat.setTransitionName(binding.container, "lyrics") setStatusbarColorAuto() setTaskDescriptionColorAuto() setNavigationbarColorAuto() setupWakelock() - toolbar.setBackgroundColor(surfaceColor()) - ToolbarContentTintHelper.colorBackButton(toolbar) - setSupportActionBar(toolbar) + binding.toolbar.setBackgroundColor(surfaceColor()) + binding.tabLyrics.setBackgroundColor(surfaceColor()) + binding.container.setBackgroundColor(surfaceColor()) + ToolbarContentTintHelper.colorBackButton(binding.toolbar) + setSupportActionBar(binding.toolbar) + setupViews() - updateHelper = MusicProgressViewUpdateHelper(this, 500, 1000) - setupLyricsView() } - private fun setupLyricsView() { - lyricsView.apply { - setCurrentColor(ThemeStore.accentColor(context)) - setTimeTextColor(ThemeStore.accentColor(context)) - setTimelineColor(ThemeStore.accentColor(context)) - setTimelineTextColor(ThemeStore.accentColor(context)) - setDraggable(true, LrcView.OnPlayClickListener { - MusicPlayerRemote.seekTo(it.toInt()) - return@OnPlayClickListener true - }) - } + + private fun setupViews() { + binding.lyricsPager.adapter = lyricsSectionsAdapter + TabLayoutMediator(binding.tabLyrics, binding.lyricsPager) { tab, position -> + tab.text = when (position) { + 0 -> "Synced Lyrics" + 1 -> "Normal Lyrics" + else -> "" + } + }.attach() +// lyricsPager.isUserInputEnabled = false + + binding.tabLyrics.setSelectedTabIndicatorColor(ThemeStore.accentColor(this)) + binding.tabLyrics.setTabTextColors(textColorSecondary(), accentColor()) } - override fun onResume() { - super.onResume() - updateHelper.start() - } - - override fun onPause() { - super.onPause() - updateHelper.stop() - } - - override fun onUpdateProgressViews(progress: Int, total: Int) { - lyricsView.updateTime(progress.toLong()) - } - - private fun loadLRCLyrics() { - lyricsView.setLabel("Empty") - val song = MusicPlayerRemote.currentSong - if (LyricUtil.isLrcOriginalFileExist(song.data)) { - lyricsView.loadLrc(LyricUtil.getLocalLyricOriginalFile(song.data)) - } else if (LyricUtil.isLrcFileExist(song.title, song.artistName)) { - lyricsView.loadLrc(LyricUtil.getLocalLyricFile(song.title, song.artistName)) - } - } override fun onPlayingMetaChanged() { super.onPlayingMetaChanged() updateTitleSong() - loadLRCLyrics() } override fun onServiceConnected() { super.onServiceConnected() updateTitleSong() - loadLRCLyrics() } private fun updateTitleSong() { song = MusicPlayerRemote.currentSong - toolbar.title = song.title - toolbar.subtitle = song.artistName + binding.toolbar.title = song.title + binding.toolbar.subtitle = song.artistName } private fun setupWakelock() { @@ -149,8 +155,206 @@ class LyricsActivity : AbsMusicServiceActivity(), MusicProgressViewUpdateHelper. return true } if (item.itemId == R.id.action_search) { - RetroUtil.openUrl(this, googleSearchLrcUrl) + RetroUtil.openUrl( + this, when (binding.lyricsPager.currentItem) { + 0 -> syairSearchLrcUrl + 1 -> googleSearchLrcUrl + else -> googleSearchLrcUrl + } + ) + } else if (item.itemId == R.id.action_edit) { + when (binding.lyricsPager.currentItem) { + 0 -> { + editSyncedLyrics() + } + 1 -> { + editNormalLyrics() + } + } } return super.onOptionsItemSelected(item) } + + + private fun editNormalLyrics() { + var content = "" + val file = File(MusicPlayerRemote.currentSong.data) + try { + content = AudioFileIO.read(file).tagOrCreateDefault.getFirst(FieldKey.LYRICS) + } catch (e: Exception) { + e.printStackTrace() + } + + MaterialDialog(this, BottomSheet(LayoutMode.WRAP_CONTENT)).show { + title(res = R.string.edit_normal_lyrics) + input( + hintRes = R.string.paste_lyrics_here, + prefill = content, + inputType = InputType.TYPE_TEXT_FLAG_MULTI_LINE or InputType.TYPE_CLASS_TEXT + ) { _, input -> + val fieldKeyValueMap = EnumMap(FieldKey::class.java) + fieldKeyValueMap[FieldKey.LYRICS] = input.toString() + WriteTagsAsyncTask(this@LyricsActivity).execute( + LoadingInfo( + listOf(song.data), fieldKeyValueMap, null + ) + ) + } + positiveButton(res = R.string.save) { + (lyricsSectionsAdapter.fragments[1].first as NormalLyrics).loadNormalLyrics() + } + negativeButton(res = android.R.string.cancel) + } + } + + + private fun editSyncedLyrics() { + var lrcFile: File? = null + if (LyricUtil.isLrcOriginalFileExist(song.data)) { + lrcFile = LyricUtil.getLocalLyricOriginalFile(song.data) + } else if (LyricUtil.isLrcFileExist(song.title, song.artistName)) { + lrcFile = LyricUtil.getLocalLyricFile(song.title, song.artistName) + } + val content: String = LyricUtil.getStringFromLrc(lrcFile) + + MaterialDialog(this, BottomSheet(LayoutMode.WRAP_CONTENT)).show { + title(res = R.string.edit_synced_lyrics) + input( + hintRes = R.string.paste_timeframe_lyrics_here, + prefill = content, + inputType = InputType.TYPE_TEXT_FLAG_MULTI_LINE or InputType.TYPE_CLASS_TEXT + ) { _, input -> + LyricUtil.writeLrc(song, input.toString()) + } + positiveButton(res = R.string.save) { + (lyricsSectionsAdapter.fragments[0].first as SyncedLyrics).loadLRCLyrics() + } + negativeButton(res = android.R.string.cancel) + } + } + + class LyricsSectionsAdapter(fragmentActivity: FragmentActivity) : + FragmentStateAdapter(fragmentActivity) { + val fragments = listOf( + Pair(SyncedLyrics(), R.string.synced_lyrics), + Pair(NormalLyrics(), R.string.normal_lyrics) + ) + + + override fun getItemCount(): Int { + return fragments.size + } + + override fun createFragment(position: Int): Fragment { + return fragments[position].first + } + } + + class NormalLyrics : AbsMusicServiceFragment(R.layout.fragment_normal_lyrics) { + + private var _binding: FragmentNormalLyricsBinding? = null + private val binding get() = _binding!! + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + _binding = FragmentNormalLyricsBinding.bind(view) + loadNormalLyrics() + super.onViewCreated(view, savedInstanceState) + } + + fun loadNormalLyrics() { + var lyrics: String? = null + val file = File(MusicPlayerRemote.currentSong.data) + try { + lyrics = AudioFileIO.read(file).tagOrCreateDefault.getFirst(FieldKey.LYRICS) + } catch (e: Exception) { + e.printStackTrace() + } + if (lyrics.isNullOrEmpty()) { + binding.noLyricsFound.visibility = View.VISIBLE + } else { + binding.noLyricsFound.visibility = View.GONE + } + binding.normalLyrics.text = lyrics + } + + override fun onPlayingMetaChanged() { + super.onPlayingMetaChanged() + loadNormalLyrics() + } + + override fun onServiceConnected() { + super.onServiceConnected() + loadNormalLyrics() + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + } + + class SyncedLyrics : AbsMusicServiceFragment(R.layout.fragment_synced_lyrics), + MusicProgressViewUpdateHelper.Callback { + + private var _binding: FragmentSyncedLyricsBinding? = null + private val binding get() = _binding!! + private lateinit var updateHelper: MusicProgressViewUpdateHelper + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + updateHelper = MusicProgressViewUpdateHelper(this, 500, 1000) + _binding = FragmentSyncedLyricsBinding.bind(view) + setupLyricsView() + loadLRCLyrics() + super.onViewCreated(view, savedInstanceState) + } + + fun loadLRCLyrics() { + binding.lyricsView.setLabel("Empty") + val song = MusicPlayerRemote.currentSong + if (LyricUtil.isLrcOriginalFileExist(song.data)) { + binding.lyricsView.loadLrc(LyricUtil.getLocalLyricOriginalFile(song.data)) + } else if (LyricUtil.isLrcFileExist(song.title, song.artistName)) { + binding.lyricsView.loadLrc(LyricUtil.getLocalLyricFile(song.title, song.artistName)) + } + } + + private fun setupLyricsView() { + binding.lyricsView.apply { + setCurrentColor(ThemeStore.accentColor(context)) + setTimeTextColor(ThemeStore.accentColor(context)) + setTimelineColor(ThemeStore.accentColor(context)) + setTimelineTextColor(ThemeStore.accentColor(context)) + setDraggable(true, LrcView.OnPlayClickListener { + MusicPlayerRemote.seekTo(it.toInt()) + return@OnPlayClickListener true + }) + } + } + + override fun onUpdateProgressViews(progress: Int, total: Int) { + binding.lyricsView.updateTime(progress.toLong()) + } + + override fun onPlayingMetaChanged() { + super.onPlayingMetaChanged() + loadLRCLyrics() + } + + override fun onServiceConnected() { + super.onServiceConnected() + loadLRCLyrics() + } + + override fun onResume() { + super.onResume() + updateHelper.start() + } + + override fun onPause() { + super.onPause() + updateHelper.stop() + } + } + + } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/MainActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/MainActivity.kt index 27dd7078..4f96a696 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/MainActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/MainActivity.kt @@ -20,38 +20,15 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.net.Uri import android.os.Bundle import android.provider.MediaStore -import android.view.View import androidx.lifecycle.lifecycleScope -import androidx.navigation.ui.NavigationUI -import code.name.monkey.retromusic.ADAPTIVE_COLOR_APP -import code.name.monkey.retromusic.ALBUM_COVER_STYLE -import code.name.monkey.retromusic.ALBUM_COVER_TRANSFORM -import code.name.monkey.retromusic.BANNER_IMAGE_PATH -import code.name.monkey.retromusic.BLACK_THEME -import code.name.monkey.retromusic.CAROUSEL_EFFECT -import code.name.monkey.retromusic.CIRCULAR_ALBUM_ART -import code.name.monkey.retromusic.DESATURATED_COLOR -import code.name.monkey.retromusic.EXTRA_SONG_INFO -import code.name.monkey.retromusic.GENERAL_THEME -import code.name.monkey.retromusic.HOME_ARTIST_GRID_STYLE -import code.name.monkey.retromusic.KEEP_SCREEN_ON -import code.name.monkey.retromusic.LANGUAGE_NAME -import code.name.monkey.retromusic.LIBRARY_CATEGORIES -import code.name.monkey.retromusic.NOW_PLAYING_SCREEN_ID -import code.name.monkey.retromusic.PROFILE_IMAGE_PATH -import code.name.monkey.retromusic.R -import code.name.monkey.retromusic.ROUND_CORNERS -import code.name.monkey.retromusic.TAB_TEXT_MODE -import code.name.monkey.retromusic.TOGGLE_ADD_CONTROLS -import code.name.monkey.retromusic.TOGGLE_FULL_SCREEN -import code.name.monkey.retromusic.TOGGLE_GENRE -import code.name.monkey.retromusic.TYPE_HOME_BANNER -import code.name.monkey.retromusic.TOGGLE_SEPARATE_LINE -import code.name.monkey.retromusic.TOGGLE_VOLUME -import code.name.monkey.retromusic.USER_NAME -import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity +import androidx.navigation.ui.setupWithNavController +import code.name.monkey.retromusic.* +import code.name.monkey.retromusic.activities.base.AbsCastActivity +import code.name.monkey.retromusic.databinding.SlidingMusicPanelLayoutBinding import code.name.monkey.retromusic.extensions.extra import code.name.monkey.retromusic.extensions.findNavController +import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewFragment +import code.name.monkey.retromusic.fragments.home.HomeFragment import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.SearchQueryHelper.getSongs import code.name.monkey.retromusic.model.CategoryInfo @@ -64,13 +41,13 @@ import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.launch import org.koin.android.ext.android.get -class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeListener { +class MainActivity : AbsCastActivity(), OnSharedPreferenceChangeListener { companion object { const val TAG = "MainActivity" const val EXPAND_PANEL = "expand_panel" } - override fun createContentView(): View { + override fun createContentView(): SlidingMusicPanelLayoutBinding { return wrapSlidingMusicPanel() } @@ -98,10 +75,50 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis val categoryInfo: CategoryInfo = PreferenceUtil.libraryCategory.first { it.visible } if (categoryInfo.visible) { - navGraph.startDestination = categoryInfo.category.id + navGraph.setStartDestination( + if (PreferenceUtil.rememberLastTab) { + PreferenceUtil.lastTab.let { + if (it == 0) { + categoryInfo.category.id + } else { + it + } + } + } else categoryInfo.category.id + ) } navController.graph = navGraph - NavigationUI.setupWithNavController(getBottomNavigationView(), navController) + getBottomNavigationView().setupWithNavController(navController) + // Scroll Fragment to top + getBottomNavigationView().setOnItemReselectedListener { + supportFragmentManager.findFragmentById(R.id.fragment_container)?.childFragmentManager?.fragments?.get(0) + .also { + if (it is AbsRecyclerViewFragment<*, *>) { + it.scrollToTop() + } + if (it is HomeFragment) { + it.scrollToTop() + } + } + } + navController.addOnDestinationChangedListener { _, destination, _ -> + when (destination.id) { + R.id.action_home, R.id.action_song, R.id.action_album, R.id.action_artist, R.id.action_folder, R.id.action_playlist, R.id.action_genre -> { + // Save the last tab + if (PreferenceUtil.rememberLastTab) { + saveTab(destination.id) + } + // Show Bottom Navigation Bar + setBottomBarVisibility(true) + } + else -> setBottomBarVisibility(false) // Hide Bottom Navigation Bar + } + + } + } + + private fun saveTab(id: Int) { + PreferenceUtil.lastTab = id } override fun onSupportNavigateUp(): Boolean = @@ -112,6 +129,8 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis PreferenceUtil.registerOnSharedPreferenceChangedListener(this) val expand = extra(EXPAND_PANEL).value ?: false if (expand && PreferenceUtil.isExpandPanel) { + setBottomBarVisibility(false) + fromNotification = true expandPanel() intent.removeExtra(EXPAND_PANEL) } @@ -123,7 +142,7 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis } override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { - if (key == GENERAL_THEME || key == BLACK_THEME || key == ADAPTIVE_COLOR_APP || key == USER_NAME || key == TOGGLE_FULL_SCREEN || key == TOGGLE_VOLUME || key == ROUND_CORNERS || key == CAROUSEL_EFFECT || key == NOW_PLAYING_SCREEN_ID || key == TOGGLE_GENRE || key == BANNER_IMAGE_PATH || key == PROFILE_IMAGE_PATH || key == CIRCULAR_ALBUM_ART || key == KEEP_SCREEN_ON || key == TOGGLE_SEPARATE_LINE || key == TYPE_HOME_BANNER || key == TOGGLE_ADD_CONTROLS || key == ALBUM_COVER_STYLE || key == HOME_ARTIST_GRID_STYLE || key == ALBUM_COVER_TRANSFORM || key == DESATURATED_COLOR || key == EXTRA_SONG_INFO || key == TAB_TEXT_MODE || key == LANGUAGE_NAME || key == LIBRARY_CATEGORIES) { + if (key == GENERAL_THEME || key == BLACK_THEME || key == ADAPTIVE_COLOR_APP || key == USER_NAME || key == TOGGLE_FULL_SCREEN || key == TOGGLE_VOLUME || key == ROUND_CORNERS || key == CAROUSEL_EFFECT || key == NOW_PLAYING_SCREEN_ID || key == TOGGLE_GENRE || key == BANNER_IMAGE_PATH || key == PROFILE_IMAGE_PATH || key == CIRCULAR_ALBUM_ART || key == KEEP_SCREEN_ON || key == TOGGLE_SEPARATE_LINE || key == TOGGLE_HOME_BANNER || key == TOGGLE_ADD_CONTROLS || key == ALBUM_COVER_STYLE || key == HOME_ARTIST_GRID_STYLE || key == ALBUM_COVER_TRANSFORM || key == DESATURATED_COLOR || key == EXTRA_SONG_INFO || key == TAB_TEXT_MODE || key == LANGUAGE_NAME || key == LIBRARY_CATEGORIES) { postRecreate() } } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/PermissionActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/PermissionActivity.kt index 543ca1c3..f61eb518 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/PermissionActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/PermissionActivity.kt @@ -14,44 +14,51 @@ */ package code.name.monkey.retromusic.activities +import android.Manifest import android.content.Intent +import android.content.pm.PackageManager +import android.content.res.ColorStateList import android.net.Uri +import android.os.Build import android.os.Bundle import android.provider.Settings +import android.view.View +import androidx.annotation.RequiresApi import androidx.core.text.HtmlCompat import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.util.VersionUtils -import code.name.monkey.retromusic.R import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity +import code.name.monkey.retromusic.databinding.ActivityPermissionBinding import code.name.monkey.retromusic.extensions.accentBackgroundColor import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.util.RingtoneManager -import kotlinx.android.synthetic.main.activity_permission.* -import kotlinx.android.synthetic.main.fragment_library.appNameText class PermissionActivity : AbsMusicServiceActivity() { + private lateinit var binding: ActivityPermissionBinding + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView((R.layout.activity_permission)) + binding = ActivityPermissionBinding.inflate(layoutInflater) + setContentView(binding.root) setStatusbarColorAuto() setNavigationbarColorAuto() setLightNavigationBar(true) setTaskDescriptionColorAuto() setupTitle() - storagePermission.setButtonClick { + binding.storagePermission.setButtonClick { requestPermissions() } - if (VersionUtils.hasMarshmallow()) audioPermission.show() - audioPermission.setButtonClick { + if (VersionUtils.hasMarshmallow()) binding.audioPermission.show() + binding.audioPermission.setButtonClick { if (RingtoneManager.requiresDialog(this@PermissionActivity)) { val intent = Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS) intent.data = Uri.parse("package:" + applicationContext.packageName) startActivity(intent) } } - finish.accentBackgroundColor() - finish.setOnClickListener { + binding.finish.accentBackgroundColor() + binding.finish.setOnClickListener { if (hasPermissions()) { startActivity( Intent(this, MainActivity::class.java).addFlags( @@ -71,6 +78,32 @@ class PermissionActivity : AbsMusicServiceActivity() { "Hello there!
Welcome to Retro Music", HtmlCompat.FROM_HTML_MODE_COMPACT ) - appNameText.text = appName + binding.appNameText.text = appName + } + + @RequiresApi(Build.VERSION_CODES.M) + override fun onResume() { + if (hasStoragePermission()) { + binding.storagePermission.checkImage.visibility = View.VISIBLE + binding.storagePermission.checkImage.imageTintList = + ColorStateList.valueOf(ThemeStore.accentColor(this)) + } + if (hasAudioPermission()) { + binding.audioPermission.checkImage.visibility = View.VISIBLE + binding.audioPermission.checkImage.imageTintList = + ColorStateList.valueOf(ThemeStore.accentColor(this)) + } + + super.onResume() + } + + @RequiresApi(Build.VERSION_CODES.M) + private fun hasStoragePermission(): Boolean { + return checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED + } + + @RequiresApi(Build.VERSION_CODES.M) + private fun hasAudioPermission(): Boolean { + return Settings.System.canWrite(this) } } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/PlayingQueueActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/PlayingQueueActivity.kt index e8a24424..70b45c24 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/PlayingQueueActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/PlayingQueueActivity.kt @@ -24,6 +24,7 @@ import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.retromusic.R import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity import code.name.monkey.retromusic.adapter.song.PlayingQueueAdapter +import code.name.monkey.retromusic.databinding.ActivityPlayingQueueBinding import code.name.monkey.retromusic.extensions.accentColor import code.name.monkey.retromusic.extensions.surfaceColor import code.name.monkey.retromusic.helper.MusicPlayerRemote @@ -34,10 +35,10 @@ import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropM import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils -import kotlinx.android.synthetic.main.activity_playing_queue.* open class PlayingQueueActivity : AbsMusicServiceActivity() { + private lateinit var binding: ActivityPlayingQueueBinding private var wrappedAdapter: RecyclerView.Adapter<*>? = null private var recyclerViewDragDropManager: RecyclerViewDragDropManager? = null private var recyclerViewSwipeManager: RecyclerViewSwipeManager? = null @@ -56,7 +57,8 @@ open class PlayingQueueActivity : AbsMusicServiceActivity() { override fun onCreate(savedInstanceState: Bundle?) { setDrawUnderStatusBar() super.onCreate(savedInstanceState) - setContentView(R.layout.activity_playing_queue) + binding = ActivityPlayingQueueBinding.inflate(layoutInflater) + setContentView(binding.root) setStatusbarColorAuto() setNavigationbarColorAuto() setTaskDescriptionColorAuto() @@ -65,7 +67,7 @@ open class PlayingQueueActivity : AbsMusicServiceActivity() { setupToolbar() setUpRecyclerView() - clearQueue.setOnClickListener { + binding.clearQueue.setOnClickListener { MusicPlayerRemote.clearQueue() } checkForPadding() @@ -100,25 +102,25 @@ open class PlayingQueueActivity : AbsMusicServiceActivity() { linearLayoutManager = LinearLayoutManager(this) - recyclerView.layoutManager = linearLayoutManager - recyclerView.adapter = wrappedAdapter - recyclerView.itemAnimator = animator - recyclerViewTouchActionGuardManager?.attachRecyclerView(recyclerView) - recyclerViewDragDropManager?.attachRecyclerView(recyclerView) - recyclerViewSwipeManager?.attachRecyclerView(recyclerView) + binding.recyclerView.layoutManager = linearLayoutManager + binding.recyclerView.adapter = wrappedAdapter + binding.recyclerView.itemAnimator = animator + recyclerViewTouchActionGuardManager?.attachRecyclerView(binding.recyclerView) + recyclerViewDragDropManager?.attachRecyclerView(binding.recyclerView) + recyclerViewSwipeManager?.attachRecyclerView(binding.recyclerView) linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0) - recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { + binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) if (dy > 0) { - clearQueue.shrink() + binding.clearQueue.shrink() } else if (dy < 0) { - clearQueue.extend() + binding.clearQueue.extend() } } }) - ThemedFastScroller.create(recyclerView) + ThemedFastScroller.create(binding.recyclerView) } private fun checkForPadding() { @@ -140,7 +142,7 @@ open class PlayingQueueActivity : AbsMusicServiceActivity() { } private fun updateCurrentSong() { - toolbar.subtitle = getUpNextAndQueueTime() + binding.toolbar.subtitle = getUpNextAndQueueTime() } override fun onPlayingMetaChanged() { @@ -150,7 +152,7 @@ open class PlayingQueueActivity : AbsMusicServiceActivity() { private fun updateQueuePosition() { playingQueueAdapter?.setCurrent(MusicPlayerRemote.position) resetToCurrentPosition() - toolbar.subtitle = getUpNextAndQueueTime() + binding.toolbar.subtitle = getUpNextAndQueueTime() } private fun updateQueue() { @@ -159,7 +161,7 @@ open class PlayingQueueActivity : AbsMusicServiceActivity() { } private fun resetToCurrentPosition() { - recyclerView.stopScroll() + binding.recyclerView.stopScroll() linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0) } @@ -188,15 +190,15 @@ open class PlayingQueueActivity : AbsMusicServiceActivity() { } private fun setupToolbar() { - toolbar.subtitle = getUpNextAndQueueTime() - toolbar.setBackgroundColor(surfaceColor()) - setSupportActionBar(toolbar) - clearQueue.backgroundTintList = ColorStateList.valueOf(accentColor()) + binding.toolbar.subtitle = getUpNextAndQueueTime() + binding.toolbar.setBackgroundColor(surfaceColor()) + setSupportActionBar(binding.toolbar) + binding.clearQueue.backgroundTintList = ColorStateList.valueOf(accentColor()) ColorStateList.valueOf( MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(accentColor())) ).apply { - clearQueue.setTextColor(this) - clearQueue.iconTint = this + binding.clearQueue.setTextColor(this) + binding.clearQueue.iconTint = this } } } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/PurchaseActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/PurchaseActivity.kt index 573ab253..c611d2f2 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/PurchaseActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/PurchaseActivity.kt @@ -29,43 +29,45 @@ import code.name.monkey.retromusic.BuildConfig import code.name.monkey.retromusic.Constants.PRO_VERSION_PRODUCT_ID import code.name.monkey.retromusic.R import code.name.monkey.retromusic.activities.base.AbsBaseActivity +import code.name.monkey.retromusic.databinding.ActivityProVersionBinding import com.anjlab.android.iab.v3.BillingProcessor import com.anjlab.android.iab.v3.TransactionDetails import java.lang.ref.WeakReference -import kotlinx.android.synthetic.main.activity_pro_version.* class PurchaseActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler { + private lateinit var binding: ActivityProVersionBinding private lateinit var billingProcessor: BillingProcessor private var restorePurchaseAsyncTask: AsyncTask<*, *, *>? = null override fun onCreate(savedInstanceState: Bundle?) { setDrawUnderStatusBar() super.onCreate(savedInstanceState) - setContentView(R.layout.activity_pro_version) + binding = ActivityProVersionBinding.inflate(layoutInflater) + setContentView(binding.root) setStatusbarColor(Color.TRANSPARENT) setLightStatusbar(false) setNavigationbarColor(Color.BLACK) setLightNavigationBar(false) - toolbar.navigationIcon?.setTint(Color.WHITE) - toolbar.setNavigationOnClickListener { onBackPressed() } + binding.toolbar.navigationIcon?.setTint(Color.WHITE) + binding.toolbar.setNavigationOnClickListener { onBackPressed() } - restoreButton.isEnabled = false - purchaseButton.isEnabled = false + binding.restoreButton.isEnabled = false + binding.purchaseButton.isEnabled = false billingProcessor = BillingProcessor(this, BuildConfig.GOOGLE_PLAY_LICENSING_KEY, this) - MaterialUtil.setTint(purchaseButton, true) + MaterialUtil.setTint(binding.purchaseButton, true) - restoreButton.setOnClickListener { + binding.restoreButton.setOnClickListener { if (restorePurchaseAsyncTask == null || restorePurchaseAsyncTask!!.status != AsyncTask.Status.RUNNING) { restorePurchase() } } - purchaseButton.setOnClickListener { + binding.purchaseButton.setOnClickListener { billingProcessor.purchase(this@PurchaseActivity, PRO_VERSION_PRODUCT_ID) } - bannerContainer.backgroundTintList = + binding.bannerContainer.backgroundTintList = ColorStateList.valueOf(ThemeStore.accentColor(this)) } @@ -99,8 +101,8 @@ class PurchaseActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler { } override fun onBillingInitialized() { - restoreButton.isEnabled = true - purchaseButton.isEnabled = true + binding.restoreButton.isEnabled = true + binding.purchaseButton.isEnabled = true } public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/SettingsActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/SettingsActivity.kt index 4dc879e0..54d31bb4 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/SettingsActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/SettingsActivity.kt @@ -23,16 +23,19 @@ import code.name.monkey.appthemehelper.util.VersionUtils import code.name.monkey.retromusic.R import code.name.monkey.retromusic.activities.base.AbsBaseActivity import code.name.monkey.retromusic.appshortcuts.DynamicShortcutManager +import code.name.monkey.retromusic.databinding.ActivitySettingsBinding import code.name.monkey.retromusic.extensions.applyToolbar import code.name.monkey.retromusic.extensions.findNavController -import com.afollestad.materialdialogs.color.ColorChooserDialog -import kotlinx.android.synthetic.main.activity_settings.* +import com.afollestad.materialdialogs.MaterialDialog +import com.afollestad.materialdialogs.color.ColorCallback -class SettingsActivity : AbsBaseActivity(), ColorChooserDialog.ColorCallback { +class SettingsActivity : AbsBaseActivity(), ColorCallback { + private lateinit var binding: ActivitySettingsBinding override fun onCreate(savedInstanceState: Bundle?) { setDrawUnderStatusBar() super.onCreate(savedInstanceState) - setContentView(R.layout.activity_settings) + binding = ActivitySettingsBinding.inflate(layoutInflater) + setContentView(binding.root) setStatusbarColorAuto() setNavigationbarColorAuto() setLightNavigationBar(true) @@ -41,10 +44,11 @@ class SettingsActivity : AbsBaseActivity(), ColorChooserDialog.ColorCallback { private fun setupToolbar() { setTitle(R.string.action_settings) - applyToolbar(toolbar) + applyToolbar(binding.toolbar) val navController: NavController = findNavController(R.id.contentFrame) navController.addOnDestinationChangedListener { _, _, _ -> - toolbar.title = navController.currentDestination?.let { getStringFromDestination(it) } + binding.toolbar.title = + navController.currentDestination?.let { getStringFromDestination(it) } } } @@ -68,24 +72,18 @@ class SettingsActivity : AbsBaseActivity(), ColorChooserDialog.ColorCallback { return findNavController(R.id.contentFrame).navigateUp() || super.onSupportNavigateUp() } - override fun onColorSelection(dialog: ColorChooserDialog, selectedColor: Int) { - when (dialog.title) { - R.string.accent_color -> { - ThemeStore.editTheme(this).accentColor(selectedColor).commit() - if (VersionUtils.hasNougatMR()) - DynamicShortcutManager(this).updateDynamicShortcuts() - } - } - recreate() - } - - override fun onColorChooserDismissed(dialog: ColorChooserDialog) { - } - override fun onOptionsItemSelected(item: MenuItem): Boolean { if (item.itemId == android.R.id.home) { onBackPressed() } return super.onOptionsItemSelected(item) } + + override fun invoke(dialog: MaterialDialog, color: Int) { + ThemeStore.editTheme(this).accentColor(color).commit() + if (VersionUtils.hasNougatMR()) + DynamicShortcutManager(this).updateDynamicShortcuts() + + recreate() + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/ShareInstagramStory.kt b/app/src/main/java/code/name/monkey/retromusic/activities/ShareInstagramStory.kt index 1bcd169c..b3d31052 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/ShareInstagramStory.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/ShareInstagramStory.kt @@ -26,15 +26,14 @@ import androidx.core.view.drawToBitmap import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.MaterialValueHelper -import code.name.monkey.retromusic.R import code.name.monkey.retromusic.activities.base.AbsBaseActivity +import code.name.monkey.retromusic.databinding.ActivityShareInstagramBinding +import code.name.monkey.retromusic.glide.GlideApp +import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.RetroMusicColoredTarget -import code.name.monkey.retromusic.glide.SongGlideRequest import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.Share import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import com.bumptech.glide.Glide -import kotlinx.android.synthetic.main.activity_share_instagram.* /** * Created by hemanths on 2020-02-02. @@ -42,6 +41,8 @@ import kotlinx.android.synthetic.main.activity_share_instagram.* class ShareInstagramStory : AbsBaseActivity() { + private lateinit var binding: ActivityShareInstagramBinding + companion object { const val EXTRA_SONG = "extra_song" } @@ -57,32 +58,33 @@ class ShareInstagramStory : AbsBaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { setDrawUnderStatusBar() super.onCreate(savedInstanceState) - setContentView(R.layout.activity_share_instagram) + binding = ActivityShareInstagramBinding.inflate(layoutInflater) + setContentView(binding.root) setStatusbarColor(Color.TRANSPARENT) setNavigationbarColor(Color.BLACK) - toolbar.setBackgroundColor(Color.TRANSPARENT) - setSupportActionBar(toolbar) + binding.toolbar.setBackgroundColor(Color.TRANSPARENT) + setSupportActionBar(binding.toolbar) val song = intent.extras?.getParcelable(EXTRA_SONG) song?.let { songFinal -> - SongGlideRequest.Builder.from(Glide.with(this), songFinal) - .checkIgnoreMediaStore(this@ShareInstagramStory) - .generatePalette(this@ShareInstagramStory) - .build() - .into(object : RetroMusicColoredTarget(image) { + GlideApp.with(this) + .asBitmapPalette() + .songCoverOptions(songFinal) + .load(RetroGlideExtension.getSongModel(songFinal)) + .into(object : RetroMusicColoredTarget(binding.image) { override fun onColorReady(colors: MediaNotificationProcessor) { val isColorLight = ColorUtil.isColorLight(colors.backgroundColor) setColors(isColorLight, colors.backgroundColor) } }) - shareTitle.text = songFinal.title - shareText.text = songFinal.artistName - shareButton.setOnClickListener { + binding.shareTitle.text = songFinal.title + binding.shareText.text = songFinal.artistName + binding.shareButton.setOnClickListener { val path: String = Media.insertImage( contentResolver, - mainContent.drawToBitmap(Bitmap.Config.ARGB_8888), + binding.mainContent.drawToBitmap(Bitmap.Config.ARGB_8888), "Design", null ) val uri = Uri.parse(path) @@ -92,24 +94,25 @@ class ShareInstagramStory : AbsBaseActivity() { ) } } - shareButton.setTextColor( + binding.shareButton.setTextColor( MaterialValueHelper.getPrimaryTextColor( this, ColorUtil.isColorLight(ThemeStore.accentColor(this)) ) ) - shareButton.backgroundTintList = ColorStateList.valueOf(ThemeStore.accentColor(this)) + binding.shareButton.backgroundTintList = + ColorStateList.valueOf(ThemeStore.accentColor(this)) } private fun setColors(colorLight: Boolean, color: Int) { setLightStatusbar(colorLight) - toolbar.setTitleTextColor( + binding.toolbar.setTitleTextColor( MaterialValueHelper.getPrimaryTextColor( this@ShareInstagramStory, colorLight ) ) - toolbar.navigationIcon?.setTintList( + binding.toolbar.navigationIcon?.setTintList( ColorStateList.valueOf( MaterialValueHelper.getPrimaryTextColor( this@ShareInstagramStory, @@ -117,7 +120,7 @@ class ShareInstagramStory : AbsBaseActivity() { ) ) ) - mainContent.background = + binding.mainContent.background = GradientDrawable( GradientDrawable.Orientation.TOP_BOTTOM, intArrayOf(color, Color.BLACK) diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/SupportDevelopmentActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/SupportDevelopmentActivity.kt index 7efc576a..58cddd71 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/SupportDevelopmentActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/SupportDevelopmentActivity.kt @@ -37,6 +37,7 @@ import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.retromusic.BuildConfig import code.name.monkey.retromusic.R import code.name.monkey.retromusic.activities.base.AbsBaseActivity +import code.name.monkey.retromusic.databinding.ActivityDonationBinding import code.name.monkey.retromusic.extensions.textColorPrimary import code.name.monkey.retromusic.extensions.textColorSecondary import com.anjlab.android.iab.v3.BillingProcessor @@ -44,10 +45,11 @@ import com.anjlab.android.iab.v3.SkuDetails import com.anjlab.android.iab.v3.TransactionDetails import java.lang.ref.WeakReference import java.util.* -import kotlinx.android.synthetic.main.activity_donation.* class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler { + lateinit var binding: ActivityDonationBinding + companion object { val TAG: String = SupportDevelopmentActivity::class.java.simpleName const val DONATION_PRODUCT_IDS = R.array.donation_ids @@ -72,6 +74,7 @@ class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingH override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + binding = ActivityDonationBinding.inflate(layoutInflater) setContentView(R.layout.activity_donation) setStatusbarColorAuto() @@ -82,15 +85,15 @@ class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingH setupToolbar() billingProcessor = BillingProcessor(this, BuildConfig.GOOGLE_PLAY_LICENSING_KEY, this) - TintHelper.setTint(progress, ThemeStore.accentColor(this)) - donation.setTextColor(ThemeStore.accentColor(this)) + TintHelper.setTint(binding.progress, ThemeStore.accentColor(this)) + binding.donation.setTextColor(ThemeStore.accentColor(this)) } private fun setupToolbar() { val toolbarColor = ATHUtil.resolveColor(this, R.attr.colorSurface) - toolbar.setBackgroundColor(toolbarColor) - ToolbarContentTintHelper.colorBackButton(toolbar) - setSupportActionBar(toolbar) + binding.toolbar.setBackgroundColor(toolbarColor) + ToolbarContentTintHelper.colorBackButton(binding.toolbar) + setSupportActionBar(binding.toolbar) } override fun onBillingInitialized() { @@ -146,8 +149,8 @@ private class SkuDetailsLoadAsyncTask(supportDevelopmentActivity: SupportDevelop super.onPreExecute() val supportDevelopmentActivity = weakReference.get() ?: return - supportDevelopmentActivity.progressContainer.visibility = View.VISIBLE - supportDevelopmentActivity.recyclerView.visibility = View.GONE + supportDevelopmentActivity.binding.progressContainer.visibility = View.VISIBLE + supportDevelopmentActivity.binding.recyclerView.visibility = View.GONE } override fun doInBackground(vararg params: Void): List? { @@ -166,15 +169,17 @@ private class SkuDetailsLoadAsyncTask(supportDevelopmentActivity: SupportDevelop val dialog = weakReference.get() ?: return if (skuDetails == null || skuDetails.isEmpty()) { - dialog.progressContainer.visibility = View.GONE + dialog.binding.progressContainer.visibility = View.GONE return } - dialog.progressContainer.visibility = View.GONE - dialog.recyclerView.itemAnimator = DefaultItemAnimator() - dialog.recyclerView.layoutManager = GridLayoutManager(dialog, 2) - dialog.recyclerView.adapter = SkuDetailsAdapter(dialog, skuDetails) - dialog.recyclerView.visibility = View.VISIBLE + dialog.binding.progressContainer.visibility = View.GONE + dialog.binding.recyclerView.apply { + itemAnimator = DefaultItemAnimator() + layoutManager = GridLayoutManager(dialog, 2) + adapter = SkuDetailsAdapter(dialog, skuDetails) + visibility = View.VISIBLE + } } } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/UserInfoActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/UserInfoActivity.kt index a3bf682a..1394cc93 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/UserInfoActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/UserInfoActivity.kt @@ -27,54 +27,58 @@ import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.retromusic.Constants.USER_BANNER import code.name.monkey.retromusic.Constants.USER_PROFILE -import code.name.monkey.retromusic.R import code.name.monkey.retromusic.activities.base.AbsBaseActivity +import code.name.monkey.retromusic.databinding.ActivityUserInfoBinding import code.name.monkey.retromusic.extensions.accentColor import code.name.monkey.retromusic.extensions.applyToolbar -import code.name.monkey.retromusic.glide.ProfileBannerGlideRequest -import code.name.monkey.retromusic.glide.UserProfileGlideRequest +import code.name.monkey.retromusic.glide.GlideApp +import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.util.ImageUtil import code.name.monkey.retromusic.util.PreferenceUtil import com.bumptech.glide.Glide +import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.target.Target import com.github.dhaval2404.imagepicker.ImagePicker import com.github.dhaval2404.imagepicker.constant.ImageProvider -import java.io.BufferedOutputStream -import java.io.File -import java.io.FileOutputStream -import java.io.IOException -import kotlinx.android.synthetic.main.activity_user_info.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import java.io.BufferedOutputStream +import java.io.File +import java.io.FileOutputStream +import java.io.IOException class UserInfoActivity : AbsBaseActivity() { + private lateinit var binding: ActivityUserInfoBinding + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_user_info) + binding = ActivityUserInfoBinding.inflate(layoutInflater) + setContentView(binding.root) setStatusbarColorAuto() setNavigationbarColorAuto() setTaskDescriptionColorAuto() setLightNavigationBar(true) - applyToolbar(toolbar) + applyToolbar(binding.toolbar) - nameContainer.accentColor() - name.setText(PreferenceUtil.userName) + binding.nameContainer.accentColor() + binding.name.setText(PreferenceUtil.userName) - userImage.setOnClickListener { + binding.userImage.setOnClickListener { pickNewPhoto() } - bannerImage.setOnClickListener { + binding.bannerImage.setOnClickListener { selectBannerImage() } - next.setOnClickListener { - val nameString = name.text.toString().trim { it <= ' ' } + binding.next.setOnClickListener { + val nameString = binding.name.text.toString().trim { it <= ' ' } if (TextUtils.isEmpty(nameString)) { Toast.makeText(this, "Umm you're name can't be empty!", Toast.LENGTH_SHORT).show() return@setOnClickListener @@ -86,23 +90,24 @@ class UserInfoActivity : AbsBaseActivity() { val textColor = MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(accentColor())) - next.backgroundTintList = ColorStateList.valueOf(accentColor()) - next.iconTint = ColorStateList.valueOf(textColor) - next.setTextColor(textColor) + binding.next.backgroundTintList = ColorStateList.valueOf(accentColor()) + binding.next.iconTint = ColorStateList.valueOf(textColor) + binding.next.setTextColor(textColor) loadProfile() } private fun loadProfile() { - bannerImage?.let { - ProfileBannerGlideRequest.Builder.from( - Glide.with(this), - ProfileBannerGlideRequest.getBannerModel() - ).build().into(it) + binding.bannerImage.let { + GlideApp.with(this) + .asBitmap() + .load(RetroGlideExtension.getBannerModel()) + .profileBannerOptions(RetroGlideExtension.getBannerModel()) + .into(it) } - UserProfileGlideRequest.Builder.from( - Glide.with(this), - UserProfileGlideRequest.getUserModel() - ).build().into(userImage) + GlideApp.with(this).asBitmap() + .load(RetroGlideExtension.getUserModel()) + .userProfileOptions(RetroGlideExtension.getUserModel()) + .into(binding.userImage) } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -145,31 +150,31 @@ class UserInfoActivity : AbsBaseActivity() { private fun setAndSaveBannerImage(fileUri: Uri) { Glide.with(this) - .load(fileUri) .asBitmap() + .load(fileUri) .diskCacheStrategy(DiskCacheStrategy.NONE) - .listener(object : RequestListener { - override fun onException( - e: java.lang.Exception?, - model: Any?, - target: Target?, - isFirstResource: Boolean - ): Boolean { - return false - } - + .listener(object : RequestListener { override fun onResourceReady( resource: Bitmap?, model: Any?, target: Target?, - isFromMemoryCache: Boolean, + dataSource: DataSource?, isFirstResource: Boolean ): Boolean { resource?.let { saveImage(it, USER_BANNER) } return false } + + override fun onLoadFailed( + e: GlideException?, + model: Any?, + target: Target?, + isFirstResource: Boolean + ): Boolean { + return false + } }) - .into(bannerImage) + .into(binding.bannerImage) } private fun saveImage(bitmap: Bitmap, fileName: String) { @@ -195,31 +200,31 @@ class UserInfoActivity : AbsBaseActivity() { private fun setAndSaveUserImage(fileUri: Uri) { Glide.with(this) - .load(fileUri) .asBitmap() + .load(fileUri) .diskCacheStrategy(DiskCacheStrategy.NONE) - .listener(object : RequestListener { - override fun onException( - e: java.lang.Exception?, - model: Any?, - target: Target?, - isFirstResource: Boolean - ): Boolean { - return false - } - + .listener(object : RequestListener { override fun onResourceReady( resource: Bitmap?, model: Any?, target: Target?, - isFromMemoryCache: Boolean, + dataSource: DataSource?, isFirstResource: Boolean ): Boolean { resource?.let { saveImage(it, USER_PROFILE) } return false } + + override fun onLoadFailed( + e: GlideException?, + model: Any?, + target: Target?, + isFirstResource: Boolean + ): Boolean { + return false + } }) - .into(userImage) + .into(binding.userImage) } companion object { diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/WhatsNewActivity.java b/app/src/main/java/code/name/monkey/retromusic/activities/WhatsNewActivity.java index 00d16e9a..9e37a8ee 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/WhatsNewActivity.java +++ b/app/src/main/java/code/name/monkey/retromusic/activities/WhatsNewActivity.java @@ -5,34 +5,40 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.graphics.Color; import android.os.Bundle; -import android.webkit.WebView; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.widget.Toolbar; -import code.name.monkey.appthemehelper.ThemeStore; -import code.name.monkey.appthemehelper.util.ATHUtil; -import code.name.monkey.appthemehelper.util.ColorUtil; -import code.name.monkey.appthemehelper.util.MaterialValueHelper; -import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; -import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.activities.base.AbsBaseActivity; -import code.name.monkey.retromusic.util.PreferenceUtil; +import androidx.core.widget.NestedScrollView; + import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.Locale; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; +import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; +import code.name.monkey.retromusic.Constants; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.activities.base.AbsBaseActivity; +import code.name.monkey.retromusic.databinding.ActivityWhatsNewBinding; +import code.name.monkey.retromusic.extensions.ColorExtKt; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.util.RetroUtil; + public class WhatsNewActivity extends AbsBaseActivity { private static String colorToCSS(int color) { return String.format( - Locale.getDefault(), - "rgba(%d, %d, %d, %d)", - Color.red(color), - Color.green(color), - Color.blue(color), - Color.alpha(color)); // on API 29, WebView doesn't load with hex colors + Locale.getDefault(), + "rgba(%d, %d, %d, %d)", + Color.red(color), + Color.green(color), + Color.blue(color), + Color.alpha(color)); // on API 29, WebView doesn't load with hex colors } private static void setChangelogRead(@NonNull Context context) { @@ -49,16 +55,15 @@ public class WhatsNewActivity extends AbsBaseActivity { protected void onCreate(@Nullable Bundle savedInstanceState) { setDrawUnderStatusBar(); super.onCreate(savedInstanceState); - setContentView(R.layout.activity_whats_new); + ActivityWhatsNewBinding binding = ActivityWhatsNewBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); setStatusbarColorAuto(); setNavigationbarColorAuto(); setTaskDescriptionColorAuto(); - WebView webView = findViewById(R.id.webView); - Toolbar toolbar = findViewById(R.id.toolbar); - toolbar.setBackgroundColor(ATHUtil.INSTANCE.resolveColor(this, R.attr.colorSurface)); - toolbar.setNavigationOnClickListener(v -> onBackPressed()); - ToolbarContentTintHelper.colorBackButton(toolbar); + binding.toolbar.setBackgroundColor(ATHUtil.INSTANCE.resolveColor(this, R.attr.colorSurface)); + binding.toolbar.setNavigationOnClickListener(v -> onBackPressed()); + ToolbarContentTintHelper.colorBackButton(binding.toolbar); try { StringBuilder buf = new StringBuilder(); @@ -74,38 +79,50 @@ public class WhatsNewActivity extends AbsBaseActivity { final boolean isDark = ATHUtil.INSTANCE.isWindowBackgroundDark(this); final int accentColor = ThemeStore.Companion.accentColor(this); final String backgroundColor = - colorToCSS( - ATHUtil.INSTANCE.resolveColor( - this, R.attr.colorSurface, Color.parseColor(isDark ? "#424242" : "#ffffff"))); + colorToCSS( + ATHUtil.INSTANCE.resolveColor( + this, R.attr.colorSurface, Color.parseColor(isDark ? "#424242" : "#ffffff"))); final String contentColor = colorToCSS(Color.parseColor(isDark ? "#ffffff" : "#000000")); final String textColor = colorToCSS(Color.parseColor(isDark ? "#60FFFFFF" : "#80000000")); final String accentColorString = colorToCSS(ThemeStore.Companion.accentColor(this)); + final String cardBackgroundColor = colorToCSS(Color.parseColor(isDark ? "#353535" : "#ffffff")); final String accentTextColor = - colorToCSS( - MaterialValueHelper.getPrimaryTextColor( - this, ColorUtil.INSTANCE.isColorLight(accentColor))); + colorToCSS( + MaterialValueHelper.getPrimaryTextColor( + this, ColorUtil.INSTANCE.isColorLight(accentColor))); final String changeLog = - buf.toString() - .replace( - "{style-placeholder}", - String.format( - "body { background-color: %s; color: %s; } li {color: %s;} .colorHeader {background-color: %s; color: %s;} .tag {color: %s;}", - backgroundColor, - contentColor, - textColor, - accentColorString, - accentTextColor, - accentColorString)) - .replace("{link-color}", colorToCSS(ThemeStore.Companion.accentColor(this))) - .replace( - "{link-color-active}", - colorToCSS( - ColorUtil.INSTANCE.lightenColor(ThemeStore.Companion.accentColor(this)))); - webView.loadData(changeLog, "text/html", "UTF-8"); + buf.toString() + .replace( + "{style-placeholder}", + String.format( + "body { background-color: %s; color: %s; } li {color: %s;} h3 {color: %s;} .tag {color: %s;} div{background-color: %s;}", + backgroundColor, + contentColor, + textColor, + accentColorString, + accentColorString, + cardBackgroundColor)) + .replace("{link-color}", colorToCSS(ThemeStore.Companion.accentColor(this))) + .replace( + "{link-color-active}", + colorToCSS( + ColorUtil.INSTANCE.lightenColor(ThemeStore.Companion.accentColor(this)))); + binding.webView.loadData(changeLog, "text/html", "UTF-8"); } catch (Throwable e) { - webView.loadData( - "

Unable to load

" + e.getLocalizedMessage() + "

", "text/html", "UTF-8"); + binding.webView.loadData( + "

Unable to load

" + e.getLocalizedMessage() + "

", "text/html", "UTF-8"); } setChangelogRead(this); + binding.tgFab.setOnClickListener(v -> RetroUtil.openUrl(this, Constants.TELEGRAM_CHANGE_LOG)); + ColorExtKt.accentColor(binding.tgFab); + binding.tgFab.shrink(); + binding.container.setOnScrollChangeListener((NestedScrollView.OnScrollChangeListener) (v, scrollX, scrollY, oldScrollX, oldScrollY) -> { + int dy = scrollY - oldScrollY; + if (dy > 0) { + binding.tgFab.shrink(); + } else if (dy < 0) { + binding.tgFab.extend(); + } + }); } } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsCastActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsCastActivity.kt new file mode 100644 index 00000000..68c62d13 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsCastActivity.kt @@ -0,0 +1,141 @@ +package code.name.monkey.retromusic.activities.base + +import android.os.Bundle +import android.view.ViewStub +import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.cast.CastHelper +import code.name.monkey.retromusic.cast.RetroSessionManager +import code.name.monkey.retromusic.cast.RetroWebServer +import code.name.monkey.retromusic.helper.MusicPlayerRemote +import com.google.android.gms.cast.framework.CastContext +import com.google.android.gms.cast.framework.CastSession +import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.GoogleApiAvailability +import java.util.* + + +abstract class AbsCastActivity : AbsSlidingMusicPanelActivity() { + + private var mCastSession: CastSession? = null + private lateinit var castContext: CastContext + private var webServer: RetroWebServer? = null + private var playServicesAvailable: Boolean = false + + private val sessionManagerListener by lazy { + object : RetroSessionManager { + override fun onSessionStarting(castSession: CastSession) { + invalidateOptionsMenu() + webServer = RetroWebServer.getInstance(this@AbsCastActivity) + webServer?.start() + } + + override fun onSessionStarted(castSession: CastSession, p1: String) { + invalidateOptionsMenu() + mCastSession = castSession + loadCastQueue(MusicPlayerRemote.position) + inflateCastController() + MusicPlayerRemote.isCasting = true + setAllowDragging(false) + collapsePanel() + } + + override fun onSessionEnding(p0: CastSession) { + invalidateOptionsMenu() + webServer?.stop() + } + + override fun onSessionEnded(castSession: CastSession, p1: Int) { + invalidateOptionsMenu() + if (mCastSession == castSession) { + mCastSession = null + } + MusicPlayerRemote.isCasting = false + setAllowDragging(true) + } + + override fun onSessionResumed(castSession: CastSession, p1: Boolean) { + invalidateOptionsMenu() + mCastSession = castSession + loadCastQueue(MusicPlayerRemote.position) + inflateCastController() + MusicPlayerRemote.isCasting = true + setAllowDragging(false) + collapsePanel() + } + + override fun onSessionSuspended(castSession: CastSession, p1: Int) { + invalidateOptionsMenu() + if (mCastSession == castSession) { + mCastSession = null + } + MusicPlayerRemote.isCasting = false + setAllowDragging(true) + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + playServicesAvailable = try { + GoogleApiAvailability + .getInstance().isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS + } catch (e: Exception) { + false + } + if (playServicesAvailable) { + setupCast() + } + } + + private fun setupCast() { + castContext = CastContext.getSharedInstance(this) + } + + override fun onResume() { + if (playServicesAvailable) { + castContext.sessionManager.addSessionManagerListener( + sessionManagerListener, + CastSession::class.java + ) + if (mCastSession == null) { + mCastSession = castContext.sessionManager.currentCastSession + } + } + super.onResume() + } + + override fun onStop() { + super.onStop() + mCastSession = null + } + + private fun songChanged(position: Int) { + loadCastQueue(position) + } + + fun loadCastQueue(position: Int) { + if (!MusicPlayerRemote.playingQueue.isNullOrEmpty()) { + mCastSession?.let { + CastHelper.castQueue( + it, + MusicPlayerRemote.playingQueue, + position, + MusicPlayerRemote.songProgressMillis.toLong() + ) + } + } else { + mCastSession?.let { CastHelper.castSong(it, MusicPlayerRemote.currentSong) } + } + } + + override fun onPlayingMetaChanged() { + super.onPlayingMetaChanged() + if (playServicesAvailable) { + songChanged(MusicPlayerRemote.position) + } + } + + fun inflateCastController() { + findViewById(R.id.cast_stub)?.inflate() + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsMusicServiceActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsMusicServiceActivity.kt index cef13276..1ab1e499 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsMusicServiceActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsMusicServiceActivity.kt @@ -15,12 +15,7 @@ package code.name.monkey.retromusic.activities.base import android.Manifest -import android.content.BroadcastReceiver -import android.content.ComponentName -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.content.ServiceConnection +import android.content.* import android.os.Bundle import android.os.IBinder import androidx.lifecycle.lifecycleScope @@ -30,11 +25,11 @@ import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.interfaces.IMusicServiceEventListener import code.name.monkey.retromusic.repository.RealRepository import code.name.monkey.retromusic.service.MusicService.* -import java.lang.ref.WeakReference -import java.util.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.koin.android.ext.android.inject +import java.lang.ref.WeakReference +import java.util.* abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventListener { @@ -166,6 +161,12 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventLi } } + override fun onFavoriteStateChanged() { + for (listener in mMusicServiceEventListeners) { + listener.onFavoriteStateChanged() + } + } + override fun onHasPermissionsChanged(hasPermissions: Boolean) { super.onHasPermissionsChanged(hasPermissions) val intent = Intent(MEDIA_STORE_CHANGED) @@ -194,7 +195,8 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventLi val activity = reference.get() if (activity != null && action != null) { when (action) { - FAVORITE_STATE_CHANGED, META_CHANGED -> activity.onPlayingMetaChanged() + FAVORITE_STATE_CHANGED -> activity.onFavoriteStateChanged() + META_CHANGED -> activity.onPlayingMetaChanged() QUEUE_CHANGED -> activity.onQueueChanged() PLAY_STATE_CHANGED -> activity.onPlayStateChanged() REPEAT_MODE_CHANGED -> activity.onRepeatModeChanged() diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt index 4c19aa40..a6cfe26a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt @@ -14,7 +14,6 @@ */ package code.name.monkey.retromusic.activities.base -import android.annotation.SuppressLint import android.graphics.Color import android.os.Bundle import android.view.View @@ -29,6 +28,8 @@ import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.retromusic.R import code.name.monkey.retromusic.RetroBottomSheetBehavior +import code.name.monkey.retromusic.databinding.ActivityMainContentBinding +import code.name.monkey.retromusic.databinding.SlidingMusicPanelLayoutBinding import code.name.monkey.retromusic.extensions.* import code.name.monkey.retromusic.fragments.LibraryViewModel import code.name.monkey.retromusic.fragments.MiniPlayerFragment @@ -57,12 +58,12 @@ import code.name.monkey.retromusic.model.CategoryInfo import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.views.BottomNavigationBarTinted import com.google.android.material.bottomsheet.BottomSheetBehavior.* -import kotlinx.android.synthetic.main.sliding_music_panel_layout.* import org.koin.androidx.viewmodel.ext.android.viewModel abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { companion object { val TAG: String = AbsSlidingMusicPanelActivity::class.java.simpleName + var fromNotification: Boolean = false } protected val libraryViewModel by viewModel() @@ -75,16 +76,17 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { private var lightStatusBar: Boolean = false private var lightNavigationBar: Boolean = false private var paletteColor: Int = Color.WHITE - protected abstract fun createContentView(): View + protected abstract fun createContentView(): SlidingMusicPanelLayoutBinding private val panelState: Int get() = bottomSheetBehavior.state - + private lateinit var binding: SlidingMusicPanelLayoutBinding private val bottomSheetCallbackList = object : BottomSheetCallback() { + override fun onSlide(bottomSheet: View, slideOffset: Float) { setMiniPlayerAlphaProgress(slideOffset) - dimBackground.show() - dimBackground.alpha = slideOffset + binding.dimBackground.show() + binding.dimBackground.alpha = slideOffset } override fun onStateChanged(bottomSheet: View, newState: Int) { @@ -94,9 +96,17 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { } STATE_COLLAPSED -> { onPanelCollapsed() - dimBackground.hide() + binding.dimBackground.hide() + if (fromNotification) { + hideBottomBar(MusicPlayerRemote.playingQueue.isEmpty()) + fromNotification = false + } + } + STATE_SETTLING, STATE_DRAGGING -> { + if (fromNotification) { + getBottomNavigationView().isVisible = true + } } - else -> { println("Do something") } @@ -108,23 +118,25 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(createContentView()) + binding = createContentView() + setContentView(binding.root) chooseFragmentForTheme() setupSlidingUpPanel() setupBottomSheet() updateColor() val themeColor = resolveColor(android.R.attr.windowBackground, Color.GRAY) - dimBackground.setBackgroundColor(ColorUtil.withAlpha(themeColor, 0.5f)) - dimBackground.setOnClickListener { + binding.dimBackground.setBackgroundColor(ColorUtil.withAlpha(themeColor, 0.5f)) + binding.dimBackground.setOnClickListener { println("dimBackground") collapsePanel() } } private fun setupBottomSheet() { - bottomSheetBehavior = from(slidingPanel) as RetroBottomSheetBehavior + bottomSheetBehavior = from(binding.slidingPanel) as RetroBottomSheetBehavior bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallbackList) + bottomSheetBehavior.maxWidth = resources.displayMetrics.widthPixels } override fun onResume() { @@ -142,14 +154,13 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { bottomSheetBehavior.removeBottomSheetCallback(bottomSheetCallbackList) } - @SuppressLint("InflateParams") - protected fun wrapSlidingMusicPanel(): View { - val slidingMusicPanelLayout = - layoutInflater.inflate(R.layout.sliding_music_panel_layout, null) + protected fun wrapSlidingMusicPanel(): SlidingMusicPanelLayoutBinding { + val slidingMusicPanelLayoutBinding = + SlidingMusicPanelLayoutBinding.inflate(layoutInflater) val contentContainer: ViewGroup = - slidingMusicPanelLayout.findViewById(R.id.mainContentFrame) - layoutInflater.inflate(R.layout.activity_main_content, contentContainer) - return slidingMusicPanelLayout + slidingMusicPanelLayoutBinding.mainContentFrame + ActivityMainContentBinding.inflate(layoutInflater, contentContainer, true) + return slidingMusicPanelLayoutBinding } fun collapsePanel() { @@ -166,8 +177,8 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { val alpha = 1 - progress miniPlayerFragment?.view?.alpha = alpha miniPlayerFragment?.view?.visibility = if (alpha == 0f) View.GONE else View.VISIBLE - bottomNavigationView.translationY = progress * 500 - bottomNavigationView.alpha = alpha + binding.bottomNavigationView.translationY = progress * 500 + binding.bottomNavigationView.alpha = alpha } open fun onPanelCollapsed() { @@ -183,14 +194,14 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { } private fun setupSlidingUpPanel() { - slidingPanel.viewTreeObserver.addOnGlobalLayoutListener(object : + binding.slidingPanel.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { override fun onGlobalLayout() { - slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this) + binding.slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this) if (nowPlayingScreen != Peak) { - val params = slidingPanel.layoutParams as ViewGroup.LayoutParams + val params = binding.slidingPanel.layoutParams as ViewGroup.LayoutParams params.height = ViewGroup.LayoutParams.MATCH_PARENT - slidingPanel.layoutParams = params + binding.slidingPanel.layoutParams = params } when (panelState) { STATE_EXPANDED -> onPanelExpanded() @@ -204,16 +215,16 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { } fun getBottomNavigationView(): BottomNavigationBarTinted { - return bottomNavigationView + return binding.bottomNavigationView } override fun onServiceConnected() { super.onServiceConnected() if (MusicPlayerRemote.playingQueue.isNotEmpty()) { - slidingPanel.viewTreeObserver.addOnGlobalLayoutListener(object : + binding.slidingPanel.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { override fun onGlobalLayout() { - slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this) + binding.slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this) hideBottomBar(false) } }) @@ -305,16 +316,17 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { } fun updateTabs() { - bottomNavigationView.menu.clear() + binding.bottomNavigationView.menu.clear() val currentTabs: List = PreferenceUtil.libraryCategory for (tab in currentTabs) { if (tab.visible) { val menu = tab.category - bottomNavigationView.menu.add(0, menu.id, 0, menu.stringRes).setIcon(menu.icon) + binding.bottomNavigationView.menu.add(0, menu.id, 0, menu.stringRes) + .setIcon(menu.icon) } } - if (bottomNavigationView.menu.size() == 1) { - bottomNavigationView.hide() + if (binding.bottomNavigationView.menu.size() == 1) { + binding.bottomNavigationView.hide() } } @@ -326,28 +338,34 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { } fun setBottomBarVisibility(visible: Boolean) { - bottomNavigationView.isVisible = visible + binding.bottomNavigationView.isVisible = visible hideBottomBar(MusicPlayerRemote.playingQueue.isEmpty()) } private fun hideBottomBar(hide: Boolean) { - val heightOfBar = dip(R.dimen.mini_player_height) - val heightOfBarWithTabs = heightOfBar * 2 - val isVisible = bottomNavigationView.isVisible + val heightOfBar = + if (MusicPlayerRemote.isCasting) dip(R.dimen.cast_mini_player_height) else dip(R.dimen.mini_player_height) + val heightOfBarWithTabs = + if (MusicPlayerRemote.isCasting) dip(R.dimen.mini_cast_player_height_expanded) else dip( + R.dimen.mini_player_height_expanded + ) + val isVisible = binding.bottomNavigationView.isVisible if (hide) { bottomSheetBehavior.isHideable = true bottomSheetBehavior.peekHeight = 0 - ViewCompat.setElevation(slidingPanel, 0f) - ViewCompat.setElevation(bottomNavigationView, 10f) + ViewCompat.setElevation(binding.slidingPanel, 0f) + ViewCompat.setElevation(binding.bottomNavigationView, 10f) collapsePanel() } else { if (MusicPlayerRemote.playingQueue.isNotEmpty()) { bottomSheetBehavior.isHideable = false - ViewCompat.setElevation(slidingPanel, 10f) - ViewCompat.setElevation(bottomNavigationView, 10f) + ViewCompat.setElevation(binding.slidingPanel, 10f) + ViewCompat.setElevation(binding.bottomNavigationView, 10f) if (isVisible) { println("List") - bottomSheetBehavior.peekHeight = heightOfBarWithTabs - 22 + if (bottomSheetBehavior.state != STATE_EXPANDED) + getBottomNavigationView().translateYAnimate(0F) + bottomSheetBehavior.peekHeightAnimate(heightOfBarWithTabs) } else { println("Details") bottomSheetBehavior.peekHeight = heightOfBar @@ -356,6 +374,11 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { } } + fun setAllowDragging(allowDragging: Boolean) { + bottomSheetBehavior.setAllowDragging(allowDragging) + hideBottomBar(false) + } + private fun chooseFragmentForTheme() { nowPlayingScreen = PreferenceUtil.nowPlayingScreen diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsThemeActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsThemeActivity.kt index 780f50de..8d437159 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsThemeActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsThemeActivity.kt @@ -15,6 +15,7 @@ package code.name.monkey.retromusic.activities.base import android.content.Context +import android.content.res.Resources import android.graphics.Color import android.os.Bundle import android.os.Handler @@ -23,12 +24,12 @@ import android.view.View import android.view.WindowManager import androidx.annotation.ColorInt import androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode +import androidx.core.os.ConfigurationCompat import code.name.monkey.appthemehelper.ATH import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.common.ATHToolbarActivity import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ColorUtil -import code.name.monkey.appthemehelper.util.MaterialDialogsUtil import code.name.monkey.appthemehelper.util.VersionUtils import code.name.monkey.retromusic.LanguageContextWrapper import code.name.monkey.retromusic.R @@ -48,7 +49,7 @@ abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable { setImmersiveFullscreen() registerSystemUiVisibility() toggleScreenOn() - MaterialDialogsUtil.updateMaterialDialogsThemeSingleton(this) + //MaterialDialogsUtil.updateMaterialDialogsThemeSingleton(this) } private fun updateTheme() { @@ -213,8 +214,12 @@ abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable { override fun attachBaseContext(newBase: Context?) { val code = PreferenceUtil.languageCode - if (code != "auto") { - super.attachBaseContext(LanguageContextWrapper.wrap(newBase, Locale(code))) - } else super.attachBaseContext(newBase) + val locale = if (code == "auto") { + // Get the device default locale + ConfigurationCompat.getLocales(Resources.getSystem().configuration)[0] + } else { + Locale.forLanguageTag(code) + } + super.attachBaseContext(LanguageContextWrapper.wrap(newBase, locale)) } } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/BugReportActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/BugReportActivity.kt index 358ddf82..3780834d 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/BugReportActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/BugReportActivity.kt @@ -41,18 +41,16 @@ import code.name.monkey.retromusic.activities.bugreport.model.Report import code.name.monkey.retromusic.activities.bugreport.model.github.ExtraInfo import code.name.monkey.retromusic.activities.bugreport.model.github.GithubLogin import code.name.monkey.retromusic.activities.bugreport.model.github.GithubTarget +import code.name.monkey.retromusic.databinding.ActivityBugReportBinding import code.name.monkey.retromusic.misc.DialogAsyncTask import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.textfield.TextInputLayout -import java.io.IOException -import kotlinx.android.synthetic.main.activity_bug_report.* -import kotlinx.android.synthetic.main.bug_report_card_device_info.* -import kotlinx.android.synthetic.main.bug_report_card_report.* import org.eclipse.egit.github.core.Issue import org.eclipse.egit.github.core.client.GitHubClient import org.eclipse.egit.github.core.client.RequestException import org.eclipse.egit.github.core.service.IssueService +import java.io.IOException private const val RESULT_SUCCESS = "RESULT_OK" private const val RESULT_BAD_CREDENTIALS = "RESULT_BAD_CREDENTIALS" @@ -72,12 +70,14 @@ private annotation class Result open class BugReportActivity : AbsThemeActivity() { + private lateinit var binding: ActivityBugReportBinding private var deviceInfo: DeviceInfo? = null override fun onCreate(savedInstanceState: Bundle?) { setDrawUnderStatusBar() super.onCreate(savedInstanceState) - setContentView(R.layout.activity_bug_report) + binding = ActivityBugReportBinding.inflate(layoutInflater) + setContentView(binding.root) setStatusbarColorAuto() setNavigationbarColorAuto() setTaskDescriptionColorAuto() @@ -87,50 +87,50 @@ open class BugReportActivity : AbsThemeActivity() { if (TextUtils.isEmpty(title)) setTitle(R.string.report_an_issue) deviceInfo = DeviceInfo(this) - airTextDeviceInfo.text = deviceInfo.toString() + binding.cardDeviceInfo.airTextDeviceInfo.text = deviceInfo.toString() } private fun initViews() { val accentColor = ThemeStore.accentColor(this) val primaryColor = ATHUtil.resolveColor(this, R.attr.colorSurface) - toolbar.setBackgroundColor(primaryColor) - setSupportActionBar(toolbar) - ToolbarContentTintHelper.colorBackButton(toolbar) + binding.toolbar.setBackgroundColor(primaryColor) + setSupportActionBar(binding.toolbar) + ToolbarContentTintHelper.colorBackButton(binding.toolbar) supportActionBar?.setDisplayHomeAsUpEnabled(true) - TintHelper.setTintAuto(optionUseAccount, accentColor, false) - optionUseAccount?.setOnClickListener { - inputTitle.isEnabled = true - inputDescription.isEnabled = true - inputUsername.isEnabled = true - inputPassword.isEnabled = true + TintHelper.setTintAuto(binding.cardReport.optionUseAccount, accentColor, false) + binding.cardReport.optionUseAccount.setOnClickListener { + binding.cardReport.inputTitle.isEnabled = true + binding.cardReport.inputDescription.isEnabled = true + binding.cardReport.inputUsername.isEnabled = true + binding.cardReport.inputPassword.isEnabled = true - optionAnonymous.isChecked = false - sendFab.hide(object : FloatingActionButton.OnVisibilityChangedListener() { + binding.cardReport.optionAnonymous.isChecked = false + binding.sendFab.hide(object : FloatingActionButton.OnVisibilityChangedListener() { override fun onHidden(fab: FloatingActionButton?) { super.onHidden(fab) - sendFab.setImageResource(R.drawable.ic_send) - sendFab.show() + binding.sendFab.setImageResource(R.drawable.ic_send) + binding.sendFab.show() } }) } - TintHelper.setTintAuto(optionAnonymous, accentColor, false) - optionAnonymous.setOnClickListener { - inputTitle.isEnabled = false - inputDescription.isEnabled = false - inputUsername.isEnabled = false - inputPassword.isEnabled = false + TintHelper.setTintAuto(binding.cardReport.optionAnonymous, accentColor, false) + binding.cardReport.optionAnonymous.setOnClickListener { + binding.cardReport.inputTitle.isEnabled = false + binding.cardReport.inputDescription.isEnabled = false + binding.cardReport.inputUsername.isEnabled = false + binding.cardReport.inputPassword.isEnabled = false - optionUseAccount.isChecked = false - sendFab.hide(object : FloatingActionButton.OnVisibilityChangedListener() { + binding.cardReport.optionUseAccount.isChecked = false + binding.sendFab.hide(object : FloatingActionButton.OnVisibilityChangedListener() { override fun onHidden(fab: FloatingActionButton?) { super.onHidden(fab) - sendFab.setImageResource(R.drawable.ic_open_in_browser) - sendFab.show() + binding.sendFab.setImageResource(R.drawable.ic_open_in_browser) + binding.sendFab.show() } }) } - inputPassword.setOnEditorActionListener { _, actionId, _ -> + binding.cardReport.inputPassword.setOnEditorActionListener { _, actionId, _ -> if (actionId == EditorInfo.IME_ACTION_SEND) { reportIssue() return@setOnEditorActionListener true @@ -138,22 +138,22 @@ open class BugReportActivity : AbsThemeActivity() { false } - airTextDeviceInfo.setOnClickListener { copyDeviceInfoToClipBoard() } + binding.cardDeviceInfo.airTextDeviceInfo.setOnClickListener { copyDeviceInfoToClipBoard() } - TintHelper.setTintAuto(sendFab, accentColor, true) - sendFab.setOnClickListener { reportIssue() } + TintHelper.setTintAuto(binding.sendFab, accentColor, true) + binding.sendFab.setOnClickListener { reportIssue() } - MaterialUtil.setTint(inputLayoutTitle, false) - MaterialUtil.setTint(inputLayoutDescription, false) - MaterialUtil.setTint(inputLayoutUsername, false) - MaterialUtil.setTint(inputLayoutPassword, false) + MaterialUtil.setTint(binding.cardReport.inputLayoutTitle, false) + MaterialUtil.setTint(binding.cardReport.inputLayoutDescription, false) + MaterialUtil.setTint(binding.cardReport.inputLayoutUsername, false) + MaterialUtil.setTint(binding.cardReport.inputLayoutPassword, false) } private fun reportIssue() { - if (optionUseAccount.isChecked) { + if (binding.cardReport.optionUseAccount.isChecked) { if (!validateInput()) return - val username = inputUsername.text.toString() - val password = inputPassword.text.toString() + val username = binding.cardReport.inputUsername.text.toString() + val password = binding.cardReport.inputPassword.text.toString() sendBugReport(GithubLogin(username, password)) } else { copyDeviceInfoToClipBoard() @@ -179,34 +179,34 @@ open class BugReportActivity : AbsThemeActivity() { private fun validateInput(): Boolean { var hasErrors = false - if (optionUseAccount.isChecked) { - if (TextUtils.isEmpty(inputUsername.text)) { - setError(inputLayoutUsername, R.string.bug_report_no_username) + if (binding.cardReport.optionUseAccount.isChecked) { + if (TextUtils.isEmpty(binding.cardReport.inputUsername.text)) { + setError(binding.cardReport.inputLayoutUsername, R.string.bug_report_no_username) hasErrors = true } else { - removeError(inputLayoutUsername) + removeError(binding.cardReport.inputLayoutUsername) } - if (TextUtils.isEmpty(inputPassword.text)) { - setError(inputLayoutPassword, R.string.bug_report_no_password) + if (TextUtils.isEmpty(binding.cardReport.inputPassword.text)) { + setError(binding.cardReport.inputLayoutPassword, R.string.bug_report_no_password) hasErrors = true } else { - removeError(inputLayoutPassword) + removeError(binding.cardReport.inputLayoutPassword) } } - if (TextUtils.isEmpty(inputTitle.text)) { - setError(inputLayoutTitle, R.string.bug_report_no_title) + if (TextUtils.isEmpty(binding.cardReport.inputTitle.text)) { + setError(binding.cardReport.inputLayoutTitle, R.string.bug_report_no_title) hasErrors = true } else { - removeError(inputLayoutTitle) + removeError(binding.cardReport.inputLayoutTitle) } - if (TextUtils.isEmpty(inputDescription.text)) { - setError(inputLayoutDescription, R.string.bug_report_no_description) + if (TextUtils.isEmpty(binding.cardReport.inputDescription.text)) { + setError(binding.cardReport.inputLayoutDescription, R.string.bug_report_no_description) hasErrors = true } else { - removeError(inputLayoutDescription) + removeError(binding.cardReport.inputLayoutDescription) } return !hasErrors @@ -223,8 +223,8 @@ open class BugReportActivity : AbsThemeActivity() { private fun sendBugReport(login: GithubLogin) { if (!validateInput()) return - val bugTitle = inputTitle.text.toString() - val bugDescription = inputDescription.text.toString() + val bugTitle = binding.cardReport.inputTitle.text.toString() + val bugDescription = binding.cardReport.inputDescription.text.toString() val extraInfo = ExtraInfo() onSaveExtraInfo() diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/DeviceInfo.java b/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/DeviceInfo.java index 79e9b0a0..036cc4da 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/DeviceInfo.java +++ b/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/DeviceInfo.java @@ -5,11 +5,14 @@ import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Build; + import androidx.annotation.IntRange; -import code.name.monkey.retromusic.util.PreferenceUtil; + import java.util.Arrays; import java.util.Locale; +import code.name.monkey.retromusic.util.PreferenceUtil; + public class DeviceInfo { @SuppressLint("NewApi") diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/saf/SAFGuideActivity.java b/app/src/main/java/code/name/monkey/retromusic/activities/saf/SAFGuideActivity.java index 46fdd786..e47c770e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/saf/SAFGuideActivity.java +++ b/app/src/main/java/code/name/monkey/retromusic/activities/saf/SAFGuideActivity.java @@ -16,11 +16,14 @@ package code.name.monkey.retromusic.activities.saf; import android.os.Build; import android.os.Bundle; + import androidx.annotation.Nullable; -import code.name.monkey.retromusic.R; + import com.heinrichreimersoftware.materialintro.app.IntroActivity; import com.heinrichreimersoftware.materialintro.slide.SimpleSlide; +import code.name.monkey.retromusic.R; + /** Created by hemanths on 2019-07-31. */ public class SAFGuideActivity extends IntroActivity { diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AbsTagEditorActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AbsTagEditorActivity.kt index 0202a4b4..536dea62 100755 --- a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AbsTagEditorActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AbsTagEditorActivity.kt @@ -22,10 +22,12 @@ import android.graphics.BitmapFactory import android.net.Uri import android.os.Bundle import android.util.Log +import android.view.LayoutInflater import android.view.MenuItem -import android.view.View import android.view.animation.OvershootInterpolator +import android.widget.ImageView import androidx.appcompat.app.AlertDialog +import androidx.viewbinding.ViewBinding import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.TintHelper @@ -41,7 +43,6 @@ import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.SAFUtil import com.google.android.material.button.MaterialButton import com.google.android.material.dialog.MaterialAlertDialogBuilder -import kotlinx.android.synthetic.main.activity_album_tag_editor.* import org.jaudiotagger.audio.AudioFile import org.jaudiotagger.audio.AudioFileIO import org.jaudiotagger.tag.FieldKey @@ -49,7 +50,8 @@ import org.koin.android.ext.android.inject import java.io.File import java.util.* -abstract class AbsTagEditorActivity : AbsBaseActivity() { +abstract class AbsTagEditorActivity : AbsBaseActivity() { + abstract val editorImage: ImageView? val repository by inject() lateinit var saveFab: MaterialButton @@ -62,7 +64,11 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() { private val currentSongPath: String? = null private var savedTags: Map? = null private var savedArtworkInfo: ArtworkInfo? = null - protected abstract val contentViewLayout: Int + private var _binding: VB? = null + protected val binding: VB get() = _binding!! + + abstract val bindingInflater: (LayoutInflater) -> VB + protected abstract fun loadImageFromFile(selectedFile: Uri?) protected val show: AlertDialog @@ -187,7 +193,8 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(contentViewLayout) + _binding = bindingInflater.invoke(layoutInflater) + setContentView(binding.root) setStatusbarColorAuto() setNavigationbarColorAuto() setTaskDescriptionColorAuto() @@ -284,10 +291,6 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() { protected fun setNoImageMode() { isInNoImageMode = true - imageContainer?.visibility = View.GONE - editorImage?.visibility = View.GONE - editorImage?.isEnabled = false - setColors( intent.getIntExtra( EXTRA_PALETTE, @@ -296,6 +299,7 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() { ) } + protected fun dataChanged() { showFab() } @@ -314,9 +318,9 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() { protected fun setImageBitmap(bitmap: Bitmap?, bgColor: Int) { if (bitmap == null) { - editorImage.setImageResource(drawable.default_audio_art) + editorImage?.setImageResource(drawable.default_audio_art) } else { - editorImage.setImageBitmap(bitmap) + editorImage?.setImageBitmap(bitmap) } setColors(bgColor) } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AlbumTagEditorActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AlbumTagEditorActivity.kt index b48ab296..2a17c54c 100755 --- a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AlbumTagEditorActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AlbumTagEditorActivity.kt @@ -25,30 +25,31 @@ import android.os.Bundle import android.text.Editable import android.text.TextWatcher import android.transition.Slide +import android.view.LayoutInflater +import android.widget.ImageView import android.widget.Toast import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.MaterialUtil import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.ActivityAlbumTagEditorBinding import code.name.monkey.retromusic.extensions.appHandleColor -import code.name.monkey.retromusic.glide.palette.BitmapPaletteTranscoder +import code.name.monkey.retromusic.glide.GlideApp import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper import code.name.monkey.retromusic.model.ArtworkInfo import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.ImageUtil import code.name.monkey.retromusic.util.RetroColorUtil.generatePalette import code.name.monkey.retromusic.util.RetroColorUtil.getColor -import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.request.animation.GlideAnimation -import com.bumptech.glide.request.target.SimpleTarget -import java.util.* -import kotlinx.android.synthetic.main.activity_album_tag_editor.* +import com.bumptech.glide.request.target.ImageViewTarget +import com.bumptech.glide.request.transition.Transition import org.jaudiotagger.tag.FieldKey +import java.util.* -class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher { +class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher { - override val contentViewLayout: Int - get() = R.layout.activity_album_tag_editor + override val bindingInflater: (LayoutInflater) -> ActivityAlbumTagEditorBinding = + ActivityAlbumTagEditorBinding::inflate private fun windowEnterTransition() { val slide = Slide() @@ -62,20 +63,20 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher { override fun loadImageFromFile(selectedFile: Uri?) { - Glide.with(this@AlbumTagEditorActivity).load(selectedFile).asBitmap() - .transcode(BitmapPaletteTranscoder(this), BitmapPaletteWrapper::class.java) + + GlideApp.with(this@AlbumTagEditorActivity).asBitmapPalette().load(selectedFile) .diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true) - .into(object : SimpleTarget() { + .into(object : ImageViewTarget(binding.editorImage) { override fun onResourceReady( - resource: BitmapPaletteWrapper?, - glideAnimation: GlideAnimation? + resource: BitmapPaletteWrapper, + transition: Transition? ) { - getColor(resource?.palette, Color.TRANSPARENT) - albumArtBitmap = resource?.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) } + getColor(resource.palette, Color.TRANSPARENT) + albumArtBitmap = resource.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) } setImageBitmap( albumArtBitmap, getColor( - resource?.palette, + resource.palette, ATHUtil.resolveColor( this@AlbumTagEditorActivity, R.attr.defaultFooterColor @@ -87,11 +88,13 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher { setResult(Activity.RESULT_OK) } - override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) { - super.onLoadFailed(e, errorDrawable) - Toast.makeText(this@AlbumTagEditorActivity, e.toString(), Toast.LENGTH_LONG) + override fun onLoadFailed(errorDrawable: Drawable?) { + super.onLoadFailed(errorDrawable) + Toast.makeText(this@AlbumTagEditorActivity, "Load Failed", Toast.LENGTH_LONG) .show() } + + override fun setResource(resource: BitmapPaletteWrapper?) {} }) } @@ -99,15 +102,15 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher { private var deleteAlbumArt: Boolean = false private fun setupToolbar() { - toolbar.setBackgroundColor(ATHUtil.resolveColor(this, R.attr.colorSurface)) - setSupportActionBar(toolbar) + binding.toolbar.setBackgroundColor(ATHUtil.resolveColor(this, R.attr.colorSurface)) + setSupportActionBar(binding.toolbar) } override fun onCreate(savedInstanceState: Bundle?) { setDrawUnderStatusBar() super.onCreate(savedInstanceState) window.sharedElementsUseOverlay = true - imageContainer?.transitionName = getString(R.string.transition_album_art) + binding.imageContainer.transitionName = getString(R.string.transition_album_art) windowEnterTransition() setUpViews() setupToolbar() @@ -116,22 +119,23 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher { private fun setUpViews() { fillViewsWithFileTags() - MaterialUtil.setTint(yearContainer, false) - MaterialUtil.setTint(genreContainer, false) - MaterialUtil.setTint(albumTitleContainer, false) - MaterialUtil.setTint(albumArtistContainer, false) + MaterialUtil.setTint(binding.yearContainer, false) + MaterialUtil.setTint(binding.genreContainer, false) + MaterialUtil.setTint(binding.albumTitleContainer, false) + MaterialUtil.setTint(binding.albumArtistContainer, false) - albumText.appHandleColor().addTextChangedListener(this) - albumArtistText.appHandleColor().addTextChangedListener(this) - genreTitle.appHandleColor().addTextChangedListener(this) - yearTitle.appHandleColor().addTextChangedListener(this) + binding.albumText.appHandleColor().addTextChangedListener(this) + binding.albumArtistText.appHandleColor().addTextChangedListener(this) + binding.genreTitle.appHandleColor().addTextChangedListener(this) + binding.yearTitle.appHandleColor().addTextChangedListener(this) } private fun fillViewsWithFileTags() { - albumText.setText(albumTitle) - albumArtistText.setText(albumArtistName) - genreTitle.setText(genreName) - yearTitle.setText(songYear) + binding.albumText.setText(albumTitle) + binding.albumArtistText.setText(albumArtistName) + binding.genreTitle.setText(genreName) + binding.yearTitle.setText(songYear) + println(albumTitle + albumArtistName) } override fun loadCurrentImage() { @@ -155,7 +159,7 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher { } override fun searchImageOnWeb() { - searchWebFor(albumText.text.toString(), albumArtistText.text.toString()) + searchWebFor(binding.albumText.text.toString(), binding.albumArtistText.text.toString()) } override fun deleteImage() { @@ -169,12 +173,12 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher { override fun save() { val fieldKeyValueMap = EnumMap(FieldKey::class.java) - fieldKeyValueMap[FieldKey.ALBUM] = albumText.text.toString() + fieldKeyValueMap[FieldKey.ALBUM] = binding.albumText.text.toString() // android seems not to recognize album_artist field so we additionally write the normal artist field - fieldKeyValueMap[FieldKey.ARTIST] = albumArtistText.text.toString() - fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = albumArtistText.text.toString() - fieldKeyValueMap[FieldKey.GENRE] = genreTitle.text.toString() - fieldKeyValueMap[FieldKey.YEAR] = yearTitle.text.toString() + fieldKeyValueMap[FieldKey.ARTIST] = binding.albumArtistText.text.toString() + fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = binding.albumArtistText.text.toString() + fieldKeyValueMap[FieldKey.GENRE] = binding.genreTitle.text.toString() + fieldKeyValueMap[FieldKey.YEAR] = binding.yearTitle.text.toString() writeValuesToFiles( fieldKeyValueMap, @@ -206,6 +210,10 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher { saveFab.backgroundTintList = ColorStateList.valueOf(color) } + + override val editorImage: ImageView + get() = binding.editorImage + companion object { val TAG: String = AlbumTagEditorActivity::class.java.simpleName diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/SongTagEditorActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/SongTagEditorActivity.kt index 15a37002..b81b54ad 100755 --- a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/SongTagEditorActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/SongTagEditorActivity.kt @@ -14,91 +14,98 @@ */ package code.name.monkey.retromusic.activities.tageditor +import android.annotation.SuppressLint import android.net.Uri import android.os.Bundle import android.text.Editable import android.text.TextWatcher +import android.view.LayoutInflater +import android.widget.ImageView import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.MaterialUtil import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.ActivitySongTagEditorBinding import code.name.monkey.retromusic.extensions.appHandleColor import code.name.monkey.retromusic.repository.SongRepository -import kotlinx.android.synthetic.main.activity_song_tag_editor.* import org.jaudiotagger.tag.FieldKey import org.koin.android.ext.android.inject import java.util.* -class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher { +class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher { + + override val bindingInflater: (LayoutInflater) -> ActivitySongTagEditorBinding = + ActivitySongTagEditorBinding::inflate - override val contentViewLayout: Int - get() = R.layout.activity_song_tag_editor private val songRepository by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - - setNoImageMode() setUpViews() - toolbar.setBackgroundColor(ATHUtil.resolveColor(this, R.attr.colorSurface)) - setSupportActionBar(toolbar) + setNoImageMode() + binding.toolbar.setBackgroundColor(ATHUtil.resolveColor(this, R.attr.colorSurface)) + setSupportActionBar(binding.toolbar) } + @SuppressLint("ClickableViewAccessibility") private fun setUpViews() { fillViewsWithFileTags() - MaterialUtil.setTint(songTextContainer, false) - MaterialUtil.setTint(composerContainer, false) - MaterialUtil.setTint(albumTextContainer, false) - MaterialUtil.setTint(artistContainer, false) - MaterialUtil.setTint(albumArtistContainer, false) - MaterialUtil.setTint(yearContainer, false) - MaterialUtil.setTint(genreContainer, false) - MaterialUtil.setTint(trackNumberContainer, false) - MaterialUtil.setTint(lyricsContainer, false) + MaterialUtil.setTint(binding.songTextContainer, false) + MaterialUtil.setTint(binding.composerContainer, false) + MaterialUtil.setTint(binding.albumTextContainer, false) + MaterialUtil.setTint(binding.artistContainer, false) + MaterialUtil.setTint(binding.albumArtistContainer, false) + MaterialUtil.setTint(binding.yearContainer, false) + MaterialUtil.setTint(binding.genreContainer, false) + MaterialUtil.setTint(binding.trackNumberContainer, false) + MaterialUtil.setTint(binding.lyricsContainer, false) - songText.appHandleColor().addTextChangedListener(this) - albumText.appHandleColor().addTextChangedListener(this) - albumArtistText.appHandleColor().addTextChangedListener(this) - artistText.appHandleColor().addTextChangedListener(this) - genreText.appHandleColor().addTextChangedListener(this) - yearText.appHandleColor().addTextChangedListener(this) - trackNumberText.appHandleColor().addTextChangedListener(this) - lyricsText.appHandleColor().addTextChangedListener(this) - songComposerText.appHandleColor().addTextChangedListener(this) + binding.songText.appHandleColor().addTextChangedListener(this) + binding.albumText.appHandleColor().addTextChangedListener(this) + binding.albumArtistText.appHandleColor().addTextChangedListener(this) + binding.artistText.appHandleColor().addTextChangedListener(this) + binding.genreText.appHandleColor().addTextChangedListener(this) + binding.yearText.appHandleColor().addTextChangedListener(this) + binding.trackNumberText.appHandleColor().addTextChangedListener(this) + binding.lyricsText.appHandleColor().addTextChangedListener(this) + binding.songComposerText.appHandleColor().addTextChangedListener(this) + + binding.lyricsText.setOnTouchListener { view, _ -> + view.parent.requestDisallowInterceptTouchEvent(true) + return@setOnTouchListener false + } } private fun fillViewsWithFileTags() { - songText.setText(songTitle) - albumArtistText.setText(albumArtist) - albumText.setText(albumTitle) - artistText.setText(artistName) - genreText.setText(genreName) - yearText.setText(songYear) - trackNumberText.setText(trackNumber) - lyricsText.setText(lyrics) - songComposerText.setText(composer) + binding.songText.setText(songTitle) + binding.albumArtistText.setText(albumArtist) + binding.albumText.setText(albumTitle) + binding.artistText.setText(artistName) + binding.genreText.setText(genreName) + binding.yearText.setText(songYear) + binding.trackNumberText.setText(trackNumber) + binding.lyricsText.setText(lyrics) + binding.songComposerText.setText(composer) + println(songTitle + songYear) } - override fun loadCurrentImage() { - } + override fun loadCurrentImage() {} - override fun searchImageOnWeb() { - } + override fun searchImageOnWeb() {} - override fun deleteImage() { - } + override fun deleteImage() {} override fun save() { val fieldKeyValueMap = EnumMap(FieldKey::class.java) - fieldKeyValueMap[FieldKey.TITLE] = songText.text.toString() - fieldKeyValueMap[FieldKey.ALBUM] = albumText.text.toString() - fieldKeyValueMap[FieldKey.ARTIST] = artistText.text.toString() - fieldKeyValueMap[FieldKey.GENRE] = genreText.text.toString() - fieldKeyValueMap[FieldKey.YEAR] = yearText.text.toString() - fieldKeyValueMap[FieldKey.TRACK] = trackNumberText.text.toString() - fieldKeyValueMap[FieldKey.LYRICS] = lyricsText.text.toString() - fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = albumArtistText.text.toString() - fieldKeyValueMap[FieldKey.COMPOSER] = songComposerText.text.toString() + fieldKeyValueMap[FieldKey.TITLE] = binding.songText.text.toString() + fieldKeyValueMap[FieldKey.ALBUM] = binding.albumText.text.toString() + fieldKeyValueMap[FieldKey.ARTIST] = binding.artistText.text.toString() + fieldKeyValueMap[FieldKey.GENRE] = binding.genreText.text.toString() + fieldKeyValueMap[FieldKey.YEAR] = binding.yearText.text.toString() + fieldKeyValueMap[FieldKey.TRACK] = binding.trackNumberText.text.toString() + fieldKeyValueMap[FieldKey.LYRICS] = binding.lyricsText.text.toString() + fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = binding.albumArtistText.text.toString() + fieldKeyValueMap[FieldKey.COMPOSER] = binding.songComposerText.text.toString() writeValuesToFiles(fieldKeyValueMap, null) } @@ -120,4 +127,7 @@ class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher { companion object { val TAG: String = SongTagEditorActivity::class.java.simpleName } + + override val editorImage: ImageView? + get() = null } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/WriteTagsAsyncTask.java b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/WriteTagsAsyncTask.java index 6e71e4d6..0ae1dc8d 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/WriteTagsAsyncTask.java +++ b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/WriteTagsAsyncTask.java @@ -10,7 +10,7 @@ import android.widget.Toast; import androidx.annotation.NonNull; -import com.afollestad.materialdialogs.MaterialDialog; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; import org.jaudiotagger.audio.AudioFile; import org.jaudiotagger.audio.AudioFileIO; @@ -136,17 +136,17 @@ public class WriteTagsAsyncTask extends DialogAsyncTask implements SwipeAndDragHelper.ActionCompletionContract { diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/GenreAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/GenreAdapter.kt index c2ed6d5e..ea4e6e25 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/GenreAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/GenreAdapter.kt @@ -17,13 +17,18 @@ package code.name.monkey.retromusic.adapter import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.core.view.ViewCompat +import android.view.ViewOutlineProvider import androidx.fragment.app.FragmentActivity import androidx.recyclerview.widget.RecyclerView import code.name.monkey.retromusic.R import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder +import code.name.monkey.retromusic.glide.GlideApp +import code.name.monkey.retromusic.glide.RetroGlideExtension +import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.interfaces.IGenreClickListener import code.name.monkey.retromusic.model.Genre +import code.name.monkey.retromusic.util.MusicUtil +import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import java.util.* /** @@ -36,6 +41,15 @@ class GenreAdapter( private val mItemLayoutRes: Int, private val listener: IGenreClickListener ) : RecyclerView.Adapter() { + + init { + this.setHasStableIds(true) + } + + override fun getItemId(position: Int): Long { + return dataSet[position].id + } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { return ViewHolder(LayoutInflater.from(activity).inflate(mItemLayoutRes, parent, false)) } @@ -49,6 +63,28 @@ class GenreAdapter( genre.songCount, if (genre.songCount > 1) activity.getString(R.string.songs) else activity.getString(R.string.song) ) + loadGenreImage(genre, holder) + } + + private fun loadGenreImage(genre: Genre, holder: GenreAdapter.ViewHolder) { + val genreSong = MusicUtil.songByGenre(genre.id) + GlideApp.with(activity) + .asBitmapPalette() + .load(RetroGlideExtension.getSongModel(genreSong)) + .songCoverOptions(genreSong) + .into(object : RetroMusicColoredTarget(holder.image!!) { + override fun onColorReady(colors: MediaNotificationProcessor) { + setColors(holder, colors) + } + }) + // Just for a bit of shadow around image + holder.image?.outlineProvider = ViewOutlineProvider.BOUNDS + } + + private fun setColors(holder: ViewHolder, color: MediaNotificationProcessor) { + holder.imageContainerCard?.setCardBackgroundColor(color.backgroundColor) + holder.title?.setTextColor(color.primaryTextColor) + holder.text?.setTextColor(color.secondaryTextColor) } override fun getItemCount(): Int { @@ -62,7 +98,6 @@ class GenreAdapter( inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) { override fun onClick(v: View?) { - ViewCompat.setTransitionName(itemView, "genre") listener.onClickGenre(dataSet[layoutPosition], itemView) } } diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/HomeAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/HomeAdapter.kt index f57c45ab..85c3578b 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/HomeAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/HomeAdapter.kt @@ -22,6 +22,7 @@ import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.AppCompatTextView import androidx.core.os.bundleOf +import androidx.fragment.app.findFragment import androidx.navigation.findNavController import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.recyclerview.widget.GridLayoutManager @@ -34,14 +35,15 @@ import code.name.monkey.retromusic.adapter.album.AlbumAdapter import code.name.monkey.retromusic.adapter.artist.ArtistAdapter import code.name.monkey.retromusic.adapter.song.SongAdapter import code.name.monkey.retromusic.extensions.hide -import code.name.monkey.retromusic.glide.SongGlideRequest +import code.name.monkey.retromusic.fragments.home.HomeFragment +import code.name.monkey.retromusic.glide.GlideApp +import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.interfaces.IAlbumClickListener import code.name.monkey.retromusic.interfaces.IArtistClickListener import code.name.monkey.retromusic.interfaces.IGenreClickListener import code.name.monkey.retromusic.model.* import code.name.monkey.retromusic.util.PreferenceUtil -import com.bumptech.glide.Glide import com.google.android.material.card.MaterialCardView class HomeAdapter( @@ -82,6 +84,7 @@ class HomeAdapter( val viewHolder = holder as AlbumViewHolder viewHolder.bindView(home) viewHolder.clickableArea.setOnClickListener { + it.findFragment().setSharedAxisXTransitions() activity.findNavController(R.id.fragment_container).navigate( R.id.detailListFragment, bundleOf("type" to RECENT_ALBUMS) @@ -92,6 +95,7 @@ class HomeAdapter( val viewHolder = holder as AlbumViewHolder viewHolder.bindView(home) viewHolder.clickableArea.setOnClickListener { + it.findFragment().setSharedAxisXTransitions() activity.findNavController(R.id.fragment_container).navigate( R.id.detailListFragment, bundleOf("type" to TOP_ALBUMS) @@ -102,6 +106,7 @@ class HomeAdapter( val viewHolder = holder as ArtistViewHolder viewHolder.bindView(home) viewHolder.clickableArea.setOnClickListener { + it.findFragment().setSharedAxisXTransitions() activity.findNavController(R.id.fragment_container).navigate( R.id.detailListFragment, bundleOf("type" to RECENT_ARTISTS) @@ -112,6 +117,7 @@ class HomeAdapter( val viewHolder = holder as ArtistViewHolder viewHolder.bindView(home) viewHolder.clickableArea.setOnClickListener { + it.findFragment().setSharedAxisXTransitions() activity.findNavController(R.id.fragment_container).navigate( R.id.detailListFragment, bundleOf("type" to TOP_ARTISTS) @@ -126,6 +132,7 @@ class HomeAdapter( val viewHolder = holder as PlaylistViewHolder viewHolder.bindView(home) viewHolder.clickableArea.setOnClickListener { + it.findFragment().setSharedAxisXTransitions() activity.findNavController(R.id.fragment_container).navigate( R.id.detailListFragment, bundleOf("type" to FAVOURITES) @@ -184,17 +191,29 @@ class HomeAdapter( fun bindView(home: Home) { val color = ThemeStore.accentColor(activity) - itemView.findViewById(R.id.message).setTextColor(color) + itemView.findViewById(R.id.message).apply { + setTextColor(color) + setOnClickListener { + MusicPlayerRemote.playNext((home.arrayList as List).subList(0, 8)) + if (!MusicPlayerRemote.isPlaying) { + MusicPlayerRemote.playNextSong() + } + } + } itemView.findViewById(R.id.card6).apply { setCardBackgroundColor(ColorUtil.withAlpha(color, 0.12f)) } images.forEachIndexed { index, id -> itemView.findViewById(id).setOnClickListener { MusicPlayerRemote.playNext(home.arrayList[index] as Song) + if (!MusicPlayerRemote.isPlaying) { + MusicPlayerRemote.playNextSong() + } } - SongGlideRequest.Builder.from(Glide.with(activity), home.arrayList[index] as Song) + GlideApp.with(activity) .asBitmap() - .build() + .songCoverOptions(home.arrayList[index] as Song) + .load(RetroGlideExtension.getSongModel(home.arrayList[index] as Song)) .into(itemView.findViewById(id)) } } @@ -207,7 +226,7 @@ class HomeAdapter( val songAdapter = SongAdapter( activity, home.arrayList as MutableList, - R.layout.item_album_card, null + R.layout.item_favourite_card, null ) layoutManager = linearLayoutManager() adapter = songAdapter @@ -257,7 +276,7 @@ class HomeAdapter( bundleOf(EXTRA_ARTIST_ID to artistId), null, FragmentNavigatorExtras( - view to "artist" + view to artistId.toString() ) ) } @@ -268,7 +287,7 @@ class HomeAdapter( bundleOf(EXTRA_ALBUM_ID to albumId), null, FragmentNavigatorExtras( - view to "album" + view to albumId.toString() ) ) } diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/SearchAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/SearchAdapter.kt index a0e53380..845c2b52 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/SearchAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/SearchAdapter.kt @@ -29,15 +29,14 @@ import code.name.monkey.retromusic.* import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder import code.name.monkey.retromusic.db.PlaylistEntity import code.name.monkey.retromusic.db.PlaylistWithSongs -import code.name.monkey.retromusic.glide.AlbumGlideRequest -import code.name.monkey.retromusic.glide.ArtistGlideRequest +import code.name.monkey.retromusic.glide.GlideApp +import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.menu.SongMenuHelper import code.name.monkey.retromusic.model.* import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist import code.name.monkey.retromusic.repository.PlaylistSongsLoader import code.name.monkey.retromusic.util.MusicUtil -import com.bumptech.glide.Glide import java.util.* class SearchAdapter( @@ -52,7 +51,7 @@ class SearchAdapter( override fun getItemViewType(position: Int): Int { if (dataSet[position] is Album) return ALBUM - if (dataSet[position] is Artist) return ARTIST + if (dataSet[position] is Artist) return if ((dataSet[position] as Artist).isAlbumArtist) ALBUM_ARTIST else ARTIST if (dataSet[position] is Genre) return GENRE if (dataSet[position] is PlaylistEntity) return PLAYLIST return if (dataSet[position] is Song) SONG else HEADER @@ -66,6 +65,14 @@ class SearchAdapter( false ), viewType ) + else if (viewType == ALBUM || viewType == ARTIST || viewType== ALBUM_ARTIST) + ViewHolder( + LayoutInflater.from(activity).inflate( + R.layout.item_list_big, + parent, + false + ), viewType + ) else ViewHolder( LayoutInflater.from(activity).inflate(R.layout.item_list, parent, false), @@ -80,21 +87,23 @@ class SearchAdapter( val album = dataSet[position] as Album holder.title?.text = album.title holder.text?.text = album.artistName - AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong()) - .checkIgnoreMediaStore().build().into(holder.image) + GlideApp.with(activity).asDrawable().albumCoverOptions(album.safeGetFirstSong()).load(RetroGlideExtension.getSongModel(album.safeGetFirstSong())) + .into(holder.image!!) } ARTIST -> { holder.imageTextContainer?.isVisible = true val artist = dataSet[position] as Artist holder.title?.text = artist.name holder.text?.text = MusicUtil.getArtistInfoString(activity, artist) - ArtistGlideRequest.Builder.from(Glide.with(activity), artist).build() - .into(holder.image) + GlideApp.with(activity).asDrawable().artistImageOptions(artist).load( + RetroGlideExtension.getArtistModel(artist)).into(holder.image!!) } SONG -> { + holder.imageTextContainer?.isVisible = true val song = dataSet[position] as Song holder.title?.text = song.title holder.text?.text = song.albumName + GlideApp.with(activity).asDrawable().songCoverOptions(song).load(RetroGlideExtension.getSongModel(song)).into(holder.image!!) } GENRE -> { val genre = dataSet[position] as Genre @@ -113,6 +122,14 @@ class SearchAdapter( holder.title?.text = playlist.playlistName //holder.text?.text = MusicUtil.playlistInfoString(activity, playlist.songs) } + ALBUM_ARTIST -> { + holder.imageTextContainer?.isVisible = true + val artist = dataSet[position] as Artist + holder.title?.text = artist.name + holder.text?.text = MusicUtil.getArtistInfoString(activity, artist) + GlideApp.with(activity).asDrawable().artistImageOptions(artist).load(artist) + .into(holder.image!!) + } else -> { holder.title?.text = dataSet[position].toString() holder.title?.setTextColor(ThemeStore.accentColor(activity)) @@ -174,6 +191,12 @@ class SearchAdapter( bundleOf(EXTRA_ARTIST_ID to (item as Artist).id) ) } + ALBUM_ARTIST ->{ + activity.findNavController(R.id.fragment_container).navigate( + R.id.albumArtistDetailsFragment, + bundleOf(EXTRA_ARTIST_NAME to (item as Artist).name) + ) + } GENRE -> { activity.findNavController(R.id.fragment_container).navigate( R.id.genreDetailsFragment, @@ -202,5 +225,6 @@ class SearchAdapter( private const val SONG = 3 private const val GENRE = 4 private const val PLAYLIST = 5 + private const val ALBUM_ARTIST = 6 } } diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/SongFileAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/SongFileAdapter.kt index 5b55e02d..323eae4e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/SongFileAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/SongFileAdapter.kt @@ -24,19 +24,20 @@ import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.retromusic.R import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder +import code.name.monkey.retromusic.glide.GlideApp +import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.audiocover.AudioFileCover import code.name.monkey.retromusic.interfaces.ICabHolder import code.name.monkey.retromusic.interfaces.ICallbacks import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.RetroUtil -import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.signature.MediaStoreSignature +import me.zhanghai.android.fastscroll.PopupTextProvider import java.io.File import java.text.DecimalFormat import kotlin.math.log10 import kotlin.math.pow -import me.zhanghai.android.fastscroll.PopupTextProvider class SongFileAdapter( private val activity: AppCompatActivity, @@ -111,14 +112,14 @@ class SongFileAdapter( val error = RetroUtil.getTintedVectorDrawable( activity, R.drawable.ic_file_music, iconColor ) - Glide.with(activity) + GlideApp.with(activity) .load(AudioFileCover(file.path)) .diskCacheStrategy(DiskCacheStrategy.NONE) .error(error) .placeholder(error) - .animate(android.R.anim.fade_in) + .transition(RetroGlideExtension.getDefaultTransition()) .signature(MediaStoreSignature("", file.lastModified(), 0)) - .into(holder.image) + .into(holder.image!!) } } @@ -126,7 +127,7 @@ class SongFileAdapter( return dataSet.size } - override fun getIdentifier(position: Int): File? { + override fun getIdentifier(position: Int): File { return dataSet[position] } diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/StorageAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/StorageAdapter.kt new file mode 100644 index 00000000..98c2c632 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/StorageAdapter.kt @@ -0,0 +1,55 @@ +package code.name.monkey.retromusic.adapter + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import code.name.monkey.retromusic.R +import java.io.File + +class StorageAdapter( + val storageList: List, + val storageClickListener: StorageClickListener +) : + RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + return ViewHolder( + LayoutInflater.from(parent.context).inflate( + R.layout.item_storage, + parent, + false + ) + ) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bindData(storageList[position]) + } + + override fun getItemCount(): Int { + return storageList.size + } + + inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val title: TextView = itemView.findViewById(R.id.title) + + fun bindData(storage: Storage) { + title.text = storage.title + } + + init { + itemView.setOnClickListener { storageClickListener.onStorageClicked(storageList[bindingAdapterPosition]) } + } + } +} + +interface StorageClickListener { + fun onStorageClicked(storage: Storage) +} + +class Storage { + lateinit var title: String + lateinit var file: File +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumAdapter.kt index 869ce8bb..8f9e00f0 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumAdapter.kt @@ -24,7 +24,8 @@ import androidx.fragment.app.FragmentActivity import code.name.monkey.retromusic.R import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder -import code.name.monkey.retromusic.glide.AlbumGlideRequest +import code.name.monkey.retromusic.glide.GlideApp +import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.helper.SortOrder import code.name.monkey.retromusic.helper.menu.SongsMenuHelper @@ -35,7 +36,6 @@ import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import com.bumptech.glide.Glide import me.zhanghai.android.fastscroll.PopupTextProvider open class AlbumAdapter( @@ -73,7 +73,13 @@ open class AlbumAdapter( } protected open fun getAlbumText(album: Album): String? { - return album.artistName + return album.albumArtist.let { + if (it.isNullOrEmpty()) { + album.artistName + } else { + it + } + } } override fun onBindViewHolder(holder: ViewHolder, position: Int) { @@ -82,6 +88,7 @@ open class AlbumAdapter( holder.itemView.isActivated = isChecked holder.title?.text = getAlbumTitle(album) holder.text?.text = getAlbumText(album) + ViewCompat.setTransitionName(holder.image!!, album.id.toString()) loadAlbumCover(album, holder) } @@ -92,17 +99,17 @@ open class AlbumAdapter( holder.paletteColorContainer?.setBackgroundColor(color.backgroundColor) } holder.mask?.backgroundTintList = ColorStateList.valueOf(color.primaryTextColor) - holder.imageContainerCard?.setCardBackgroundColor(color.backgroundColor) } + holder.imageContainerCard?.setCardBackgroundColor(color.backgroundColor) + } protected open fun loadAlbumCover(album: Album, holder: ViewHolder) { if (holder.image == null) { return } - - AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong()) - .checkIgnoreMediaStore() - .generatePalette(activity) - .build() + val song = album.safeGetFirstSong() + GlideApp.with(activity).asBitmapPalette().albumCoverOptions(song) + //.checkIgnoreMediaStore() + .load(RetroGlideExtension.getSongModel(song)) .into(object : RetroMusicColoredTarget(holder.image!!) { override fun onColorReady(colors: MediaNotificationProcessor) { setColors(colors, holder) @@ -161,7 +168,6 @@ open class AlbumAdapter( inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) { init { - setImageTransitionName("Album") menu?.visibility = View.GONE } @@ -171,16 +177,13 @@ open class AlbumAdapter( toggleChecked(layoutPosition) } else { image?.let { - ViewCompat.setTransitionName(it, "album") listener?.onAlbumClick(dataSet[layoutPosition].id, it) } - } } override fun onLongClick(v: View?): Boolean { - toggleChecked(layoutPosition) - return super.onLongClick(v) + return toggleChecked(layoutPosition) } } diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumCoverPagerAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumCoverPagerAdapter.kt index f6ca4179..50841883 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumCoverPagerAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumCoverPagerAdapter.kt @@ -26,15 +26,15 @@ import androidx.lifecycle.lifecycleScope import code.name.monkey.retromusic.R import code.name.monkey.retromusic.fragments.AlbumCoverStyle import code.name.monkey.retromusic.fragments.NowPlayingScreen.* +import code.name.monkey.retromusic.glide.GlideApp +import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.RetroMusicColoredTarget -import code.name.monkey.retromusic.glide.SongGlideRequest import code.name.monkey.retromusic.misc.CustomFragmentStatePagerAdapter import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.NavigationUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import com.bumptech.glide.Glide import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -162,9 +162,10 @@ class AlbumCoverPagerAdapter( } private fun loadAlbumCover() { - SongGlideRequest.Builder.from(Glide.with(requireContext()), song) - .checkIgnoreMediaStore(requireContext()) - .generatePalette(requireContext()).build() + GlideApp.with(this).asBitmapPalette().songCoverOptions(song) + //.checkIgnoreMediaStore() + .load(RetroGlideExtension.getSongModel(song)) + .dontAnimate() .into(object : RetroMusicColoredTarget(albumCover) { override fun onColorReady(colors: MediaNotificationProcessor) { setColor(colors) diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/album/HorizontalAlbumAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/album/HorizontalAlbumAdapter.kt index 8a94b7a5..1457de7b 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/album/HorizontalAlbumAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/album/HorizontalAlbumAdapter.kt @@ -17,7 +17,8 @@ package code.name.monkey.retromusic.adapter.album import android.view.View import android.view.ViewGroup import androidx.fragment.app.FragmentActivity -import code.name.monkey.retromusic.glide.AlbumGlideRequest +import code.name.monkey.retromusic.glide.GlideApp +import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.helper.HorizontalAdapterHelper import code.name.monkey.retromusic.interfaces.IAlbumClickListener @@ -25,7 +26,6 @@ import code.name.monkey.retromusic.interfaces.ICabHolder import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import com.bumptech.glide.Glide class HorizontalAlbumAdapter( activity: FragmentActivity, @@ -49,10 +49,8 @@ class HorizontalAlbumAdapter( override fun loadAlbumCover(album: Album, holder: ViewHolder) { if (holder.image == null) return - AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong()) - .checkIgnoreMediaStore() - .generatePalette(activity) - .build() + GlideApp.with(activity).asBitmapPalette().albumCoverOptions(album.safeGetFirstSong()) + .load(RetroGlideExtension.getSongModel(album.safeGetFirstSong())) .into(object : RetroMusicColoredTarget(holder.image!!) { override fun onColorReady(colors: MediaNotificationProcessor) { setColors(colors, holder) @@ -60,7 +58,7 @@ class HorizontalAlbumAdapter( }) } - override fun getAlbumText(album: Album): String? { + override fun getAlbumText(album: Album): String { return MusicUtil.getYearString(album.year) } diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/artist/ArtistAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/artist/ArtistAdapter.kt index 395d922d..2de75bc1 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/artist/ArtistAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/artist/ArtistAdapter.kt @@ -26,25 +26,28 @@ import code.name.monkey.retromusic.R import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder import code.name.monkey.retromusic.extensions.hide -import code.name.monkey.retromusic.glide.ArtistGlideRequest +import code.name.monkey.retromusic.glide.GlideApp +import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.helper.menu.SongsMenuHelper +import code.name.monkey.retromusic.interfaces.IAlbumArtistClickListener import code.name.monkey.retromusic.interfaces.IArtistClickListener import code.name.monkey.retromusic.interfaces.ICabHolder import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.MusicUtil +import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import com.bumptech.glide.Glide -import java.util.* import me.zhanghai.android.fastscroll.PopupTextProvider +import java.util.* class ArtistAdapter( val activity: FragmentActivity, var dataSet: List, var itemLayoutRes: Int, val ICabHolder: ICabHolder?, - val IArtistClickListener: IArtistClickListener + val IArtistClickListener: IArtistClickListener, + val IAlbumArtistClickListener: IAlbumArtistClickListener? = null ) : AbsMultiSelectAdapter( activity, ICabHolder, R.menu.menu_media_selection ), PopupTextProvider { @@ -82,6 +85,13 @@ class ArtistAdapter( holder.itemView.isActivated = isChecked holder.title?.text = artist.name holder.text?.hide() + holder.image?.let { + if (PreferenceUtil.albumArtistsOnly) { + ViewCompat.setTransitionName(it, artist.name) + } else { + ViewCompat.setTransitionName(it, artist.id.toString()) + } + } loadArtistImage(artist, holder) } @@ -98,9 +108,11 @@ class ArtistAdapter( if (holder.image == null) { return } - ArtistGlideRequest.Builder.from(Glide.with(activity), artist) - .generatePalette(activity) - .build() + GlideApp.with(activity) + .asBitmapPalette() + .load(RetroGlideExtension.getArtistModel(artist)) + .artistImageOptions(artist) + .transition(RetroGlideExtension.getDefaultTransition()) .into(object : RetroMusicColoredTarget(holder.image!!) { override fun onColorReady(colors: MediaNotificationProcessor) { setColors(colors, holder) @@ -112,7 +124,7 @@ class ArtistAdapter( return dataSet.size } - override fun getIdentifier(position: Int): Artist? { + override fun getIdentifier(position: Int): Artist { return dataSet[position] } @@ -154,16 +166,19 @@ class ArtistAdapter( if (isInQuickSelectMode) { toggleChecked(layoutPosition) } else { + val artist = dataSet[layoutPosition] image?.let { - ViewCompat.setTransitionName(it, "artist") - IArtistClickListener.onArtist(dataSet[layoutPosition].id, it) + if (PreferenceUtil.albumArtistsOnly && IAlbumArtistClickListener != null) { + IAlbumArtistClickListener.onAlbumArtist(artist.name, it) + } else { + IArtistClickListener.onArtist(artist.id, it) + } } } } override fun onLongClick(v: View?): Boolean { - toggleChecked(layoutPosition) - return super.onLongClick(v) + return toggleChecked(layoutPosition) } } } diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/base/AbsMultiSelectAdapter.java b/app/src/main/java/code/name/monkey/retromusic/adapter/base/AbsMultiSelectAdapter.java index f0d431ad..581141bf 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/base/AbsMultiSelectAdapter.java +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/base/AbsMultiSelectAdapter.java @@ -1,29 +1,39 @@ package code.name.monkey.retromusic.adapter.base; +import android.annotation.SuppressLint; import android.content.Context; import android.view.Menu; import android.view.MenuItem; + import androidx.annotation.MenuRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatTextView; +import androidx.appcompat.widget.Toolbar; import androidx.recyclerview.widget.RecyclerView; -import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.interfaces.ICabHolder; + import com.afollestad.materialcab.MaterialCab; + import java.util.ArrayList; import java.util.List; -public abstract class AbsMultiSelectAdapter - extends RecyclerView.Adapter implements MaterialCab.Callback { +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.interfaces.ICabHolder; - @Nullable private final ICabHolder ICabHolder; +public abstract class AbsMultiSelectAdapter + extends RecyclerView.Adapter implements MaterialCab.Callback { + + @Nullable + private final ICabHolder ICabHolder; private final Context context; private MaterialCab cab; - private List checked; + private final List checked; private int menuRes; + private AppCompatTextView dummyText; + private int oldSize = 0; public AbsMultiSelectAdapter( - @NonNull Context context, @Nullable ICabHolder ICabHolder, @MenuRes int menuRes) { + @NonNull Context context, @Nullable ICabHolder ICabHolder, @MenuRes int menuRes) { this.ICabHolder = ICabHolder; checked = new ArrayList<>(); this.menuRes = menuRes; @@ -32,12 +42,16 @@ public abstract class AbsMultiSelectAdapter size) { + title.setTranslationY(40); + dummyText.animate().translationY(-40).alpha(0.0F).setDuration(300).start(); + } else { + title.setTranslationY(-40); + dummyText.animate().translationY(40).alpha(0.0F).setDuration(300).start(); + } + title.animate().translationY(0).alpha(1.0F).setDuration(300).start(); } + oldSize = size; } } + + private void playCreateAnim(MaterialCab materialCab) { + Toolbar cabToolbar = materialCab.getToolbar(); + int height = context.getResources().getDimensionPixelSize(R.dimen.toolbar_height); + cabToolbar.setTranslationY(-height); + cabToolbar.animate().translationYBy(height).setDuration(300).start(); + } + + private void createDummyTextView() { + if (dummyText != null) return; + dummyText = new AppCompatTextView(context); + dummyText.setSingleLine(); + dummyText.setTextAppearance(context, R.style.ToolbarTextAppearanceNormal); + Toolbar.LayoutParams l1 = new Toolbar.LayoutParams(Toolbar.LayoutParams.WRAP_CONTENT, Toolbar.LayoutParams.WRAP_CONTENT); + dummyText.setLayoutParams(l1); + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/base/MediaEntryViewHolder.java b/app/src/main/java/code/name/monkey/retromusic/adapter/base/MediaEntryViewHolder.java index 9c40491e..16dbc1c5 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/base/MediaEntryViewHolder.java +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/base/MediaEntryViewHolder.java @@ -24,11 +24,11 @@ import androidx.annotation.Nullable; import androidx.appcompat.widget.AppCompatImageView; import androidx.recyclerview.widget.RecyclerView; -import code.name.monkey.retromusic.R; - import com.google.android.material.card.MaterialCardView; import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractDraggableSwipeableItemViewHolder; +import code.name.monkey.retromusic.R; + public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHolder implements View.OnLongClickListener, View.OnClickListener { diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/PlaylistAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/PlaylistAdapter.kt index 6a941af3..afcb4ce8 100755 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/PlaylistAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/PlaylistAdapter.kt @@ -15,7 +15,6 @@ package code.name.monkey.retromusic.adapter.playlist import android.graphics.Color -import android.graphics.drawable.Drawable import android.text.TextUtils import android.view.LayoutInflater import android.view.MenuItem @@ -24,29 +23,22 @@ import android.view.ViewGroup import androidx.appcompat.widget.PopupMenu import androidx.core.view.ViewCompat import androidx.fragment.app.FragmentActivity -import androidx.lifecycle.lifecycleScope -import code.name.monkey.appthemehelper.util.ATHUtil -import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.retromusic.R import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder import code.name.monkey.retromusic.db.PlaylistEntity import code.name.monkey.retromusic.db.PlaylistWithSongs -import code.name.monkey.retromusic.db.SongEntity import code.name.monkey.retromusic.db.toSongs import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.show +import code.name.monkey.retromusic.glide.GlideApp +import code.name.monkey.retromusic.glide.playlistPreview.PlaylistPreview import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper import code.name.monkey.retromusic.helper.menu.SongsMenuHelper import code.name.monkey.retromusic.interfaces.ICabHolder import code.name.monkey.retromusic.interfaces.IPlaylistClickListener import code.name.monkey.retromusic.model.Song -import code.name.monkey.retromusic.util.AutoGeneratedPlaylistBitmap import code.name.monkey.retromusic.util.MusicUtil -import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.Dispatchers.Main -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext class PlaylistAdapter( private val activity: FragmentActivity, @@ -95,27 +87,23 @@ class PlaylistAdapter( holder.itemView.isActivated = isChecked(playlist) holder.title?.text = getPlaylistTitle(playlist.playlistEntity) holder.text?.text = getPlaylistText(playlist) - holder.image?.setImageDrawable(getIconRes()) val isChecked = isChecked(playlist) if (isChecked) { holder.menu?.hide() } else { holder.menu?.show() } - //playlistBitmapLoader(activity, holder, playlist) + GlideApp.with(activity) + .load(PlaylistPreview(playlist)) + .playlistOptions() + .into(holder.image!!) } - private fun getIconRes(): Drawable = TintHelper.createTintedDrawable( - activity, - R.drawable.ic_playlist_play, - ATHUtil.resolveColor(activity, R.attr.colorControlNormal) - ) - override fun getItemCount(): Int { return dataSet.size } - override fun getIdentifier(position: Int): PlaylistWithSongs? { + override fun getIdentifier(position: Int): PlaylistWithSongs { return dataSet[position] } @@ -141,18 +129,8 @@ class PlaylistAdapter( return songs } - private fun getSongs(playlist: PlaylistWithSongs): List = - mutableListOf().apply { - addAll(playlist.songs) - } - inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) { init { - image?.apply { - val iconPadding = - activity.resources.getDimensionPixelSize(R.dimen.list_item_image_icon_padding) - setPadding(iconPadding, iconPadding, iconPadding, iconPadding) - } menu?.setOnClickListener { view -> val popupMenu = PopupMenu(activity, view) popupMenu.inflate(R.menu.menu_item_playlist) @@ -183,37 +161,6 @@ class PlaylistAdapter( } } - private fun playlistBitmapLoader( - activity: FragmentActivity, - viewHolder: ViewHolder, - playlist: PlaylistWithSongs - ) { - - activity.lifecycleScope.launch(IO) { - val songs = playlist.songs.toSongs() - val bitmap = AutoGeneratedPlaylistBitmap.getBitmap(activity, songs, false, false) - withContext(Main) { viewHolder.image?.setImageBitmap(bitmap) } - } - - /* - override fun doInBackground(vararg params: Void?): Bitmap { - val songs = playlist.songs.toSongs() - return AutoGeneratedPlaylistBitmap.getBitmap(activity, songs, false, false) - } - - override fun onPostExecute(result: Bitmap?) { - super.onPostExecute(result) - viewHolder.image?.setImageBitmap(result) - val color = RetroColorUtil.getColor( - RetroColorUtil.generatePalette( - result - ), - ATHUtil.resolveColor(activity, R.attr.colorSurface) - ) - viewHolder.paletteColorContainer?.setBackgroundColor(color) - }*/ - } - companion object { val TAG: String = PlaylistAdapter::class.java.simpleName } diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/song/OrderablePlaylistSongAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/song/OrderablePlaylistSongAdapter.kt index eafb93f4..0e52a5d7 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/song/OrderablePlaylistSongAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/song/OrderablePlaylistSongAdapter.kt @@ -17,105 +17,99 @@ package code.name.monkey.retromusic.adapter.song import android.view.MenuItem import android.view.View import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.lifecycleScope +import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.retromusic.R -import code.name.monkey.retromusic.R.menu import code.name.monkey.retromusic.db.PlaylistEntity import code.name.monkey.retromusic.db.toSongEntity -import code.name.monkey.retromusic.db.toSongs +import code.name.monkey.retromusic.db.toSongsEntity import code.name.monkey.retromusic.dialogs.RemoveSongFromPlaylistDialog +import code.name.monkey.retromusic.extensions.applyColor +import code.name.monkey.retromusic.extensions.applyOutlineColor +import code.name.monkey.retromusic.fragments.LibraryViewModel +import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.interfaces.ICabHolder -import code.name.monkey.retromusic.model.PlaylistSong import code.name.monkey.retromusic.model.Song -import code.name.monkey.retromusic.util.ViewUtil +import com.google.android.material.button.MaterialButton import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter -import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemViewHolder import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange -import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.koin.androidx.viewmodel.ext.android.viewModel class OrderablePlaylistSongAdapter( private val playlist: PlaylistEntity, activity: FragmentActivity, - dataSet: ArrayList, + dataSet: MutableList, itemLayoutRes: Int, ICabHolder: ICabHolder?, - private val onMoveItemListener: OnMoveItemListener? -) : SongAdapter( - activity, - dataSet, - itemLayoutRes, - ICabHolder -), DraggableItemAdapter { +) : AbsOffsetSongAdapter(activity, dataSet, itemLayoutRes, ICabHolder), + DraggableItemAdapter { + + val libraryViewModel: LibraryViewModel by activity.viewModel() + val tempDataSet = dataSet init { - setMultiSelectMenuRes(menu.menu_playlists_songs_selection) + this.setHasStableIds(true) + this.setMultiSelectMenuRes(R.menu.menu_playlists_songs_selection) + } + + override fun getItemId(position: Int): Long { + // requires static value, it means need to keep the same value + // even if the item position has been changed. + return if (position != 0) { + dataSet[position - 1].id + } else { + -1 + } + + } override fun createViewHolder(view: View): SongAdapter.ViewHolder { return ViewHolder(view) } - override fun getItemId(position: Int): Long { - var positionFinal = position - positionFinal-- + override fun getItemViewType(position: Int): Int { + return if (position == 0) OFFSET_ITEM else SONG + } - var long: Long = 0 - if (positionFinal < 0) { - long = -2 - } else { - if (dataSet[positionFinal] is PlaylistSong) { - long = (dataSet[positionFinal] as PlaylistSong).idInPlayList.toLong() + override fun onBindViewHolder(holder: SongAdapter.ViewHolder, position: Int) { + if (holder.itemViewType == OFFSET_ITEM) { + val color = ThemeStore.accentColor(activity) + val viewHolder = holder as ViewHolder + viewHolder.playAction?.let { + it.setOnClickListener { + MusicPlayerRemote.openQueue(dataSet, 0, true) + } + it.applyOutlineColor(color) } + viewHolder.shuffleAction?.let { + it.setOnClickListener { + MusicPlayerRemote.openAndShuffleQueue(dataSet, true) + } + it.applyColor(color) + } + } else { + super.onBindViewHolder(holder, position - 1) } - return long } override fun onMultipleItemAction(menuItem: MenuItem, selection: List) { when (menuItem.itemId) { - R.id.action_remove_from_playlist -> { - RemoveSongFromPlaylistDialog.create(selection.toSongs(playlist.playListId)) - .show(activity.supportFragmentManager, "REMOVE_FROM_PLAYLIST") - return - } - } - super.onMultipleItemAction(menuItem, selection) - } - - override fun onCheckCanStartDrag(holder: ViewHolder, position: Int, x: Int, y: Int): Boolean { - return onMoveItemListener != null && position > 0 && (ViewUtil.hitTest( - holder.dragView!!, x, y - ) || ViewUtil.hitTest(holder.image!!, x, y)) - } - - override fun onGetItemDraggableRange(holder: ViewHolder, position: Int): ItemDraggableRange { - return ItemDraggableRange(1, dataSet.size) - } - - override fun onMoveItem(fromPosition: Int, toPosition: Int) { - if (onMoveItemListener != null && fromPosition != toPosition) { - onMoveItemListener.onMoveItem(fromPosition - 1, toPosition - 1) + R.id.action_remove_from_playlist -> RemoveSongFromPlaylistDialog.create( + selection.toSongsEntity( + playlist + ) + ) + .show(activity.supportFragmentManager, "REMOVE_FROM_PLAYLIST") + else -> super.onMultipleItemAction(menuItem, selection) } } - override fun onCheckCanDrop(draggingPosition: Int, dropPosition: Int): Boolean { - return dropPosition > 0 - } - - override fun onItemDragStarted(position: Int) { - notifyDataSetChanged() - } - - override fun onItemDragFinished(fromPosition: Int, toPosition: Int, result: Boolean) { - notifyDataSetChanged() - } - - interface OnMoveItemListener { - fun onMoveItem(fromPosition: Int, toPosition: Int) - } - - inner class ViewHolder(itemView: View) : SongAdapter.ViewHolder(itemView), - DraggableItemViewHolder { - @DraggableItemStateFlags - private var mDragStateFlags: Int = 0 + inner class ViewHolder(itemView: View) : AbsOffsetSongAdapter.ViewHolder(itemView) { + val playAction: MaterialButton? = itemView.findViewById(R.id.playAction) + val shuffleAction: MaterialButton? = itemView.findViewById(R.id.shuffleAction) override var songMenuRes: Int get() = R.menu.menu_item_playlist_song @@ -123,16 +117,6 @@ class OrderablePlaylistSongAdapter( super.songMenuRes = value } - init { - if (dragView != null) { - if (onMoveItemListener != null) { - dragView?.visibility = View.VISIBLE - } else { - dragView?.visibility = View.GONE - } - } - } - override fun onSongMenuItemClick(item: MenuItem): Boolean { when (item.itemId) { R.id.action_remove_from_playlist -> { @@ -144,13 +128,58 @@ class OrderablePlaylistSongAdapter( return super.onSongMenuItemClick(item) } - @DraggableItemStateFlags - override fun getDragStateFlags(): Int { - return mDragStateFlags + init { + dragView?.visibility = View.VISIBLE } - override fun setDragStateFlags(@DraggableItemStateFlags flags: Int) { - mDragStateFlags = flags + override fun onClick(v: View?) { + if (itemViewType == OFFSET_ITEM) { + MusicPlayerRemote.openAndShuffleQueue(dataSet, true) + return + } + super.onClick(v) + } + } + + override fun onCheckCanStartDrag(holder: ViewHolder, position: Int, x: Int, y: Int): Boolean { + if (dataSet.size == 0 or 1) { + return false + } + val dragHandle = holder.dragView ?: return false + + val handleWidth = dragHandle.width + val handleHeight = dragHandle.height + val handleLeft = dragHandle.left + val handleTop = dragHandle.top + + return (x >= handleLeft && x < handleLeft + handleWidth && + y >= handleTop && y < handleTop + handleHeight) && position != 0 + } + + override fun onMoveItem(fromPosition: Int, toPosition: Int) { + dataSet.add(toPosition - 1, dataSet.removeAt(fromPosition - 1)) + } + + + override fun onGetItemDraggableRange(holder: ViewHolder, position: Int): ItemDraggableRange { + return ItemDraggableRange(1, itemCount - 1) + } + + override fun onCheckCanDrop(draggingPosition: Int, dropPosition: Int): Boolean { + return true + } + + override fun onItemDragStarted(position: Int) { + notifyDataSetChanged() + } + + override fun onItemDragFinished(fromPosition: Int, toPosition: Int, result: Boolean) { + notifyDataSetChanged() + } + + fun saveSongs(playlistEntity: PlaylistEntity) { + activity.lifecycleScope.launch(Dispatchers.IO) { + libraryViewModel.insertSongs(dataSet.toSongsEntity(playlistEntity)) } } } diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/song/PlayingQueueAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/song/PlayingQueueAdapter.kt index f8fe7ab4..46b99c23 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/song/PlayingQueueAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/song/PlayingQueueAdapter.kt @@ -19,8 +19,9 @@ import android.view.View import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.FragmentActivity import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.glide.GlideApp +import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.RetroMusicColoredTarget -import code.name.monkey.retromusic.glide.SongGlideRequest import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote.isPlaying import code.name.monkey.retromusic.helper.MusicPlayerRemote.playNextSong @@ -29,7 +30,6 @@ import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.ViewUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import com.bumptech.glide.Glide import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags @@ -79,9 +79,8 @@ class PlayingQueueAdapter( if (holder.image == null) { return } - SongGlideRequest.Builder.from(Glide.with(activity), song) - .checkIgnoreMediaStore(activity) - .generatePalette(activity).build() + GlideApp.with(activity).asBitmapPalette().songCoverOptions(song) + .load(RetroGlideExtension.getSongModel(song)) .into(object : RetroMusicColoredTarget(holder.image!!) { override fun onColorReady(colors: MediaNotificationProcessor) { //setColors(colors, holder) diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/song/ShuffleButtonSongAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/song/ShuffleButtonSongAdapter.kt index 781a8faa..989c4116 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/song/ShuffleButtonSongAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/song/ShuffleButtonSongAdapter.kt @@ -23,6 +23,8 @@ import code.name.monkey.retromusic.extensions.applyOutlineColor import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.interfaces.ICabHolder import code.name.monkey.retromusic.model.Song +import code.name.monkey.retromusic.util.PreferenceUtil +import code.name.monkey.retromusic.util.RetroUtil import com.google.android.material.button.MaterialButton class ShuffleButtonSongAdapter( @@ -32,10 +34,15 @@ class ShuffleButtonSongAdapter( ICabHolder: ICabHolder? ) : AbsOffsetSongAdapter(activity, dataSet, itemLayoutRes, ICabHolder) { + override fun createViewHolder(view: View): SongAdapter.ViewHolder { return ViewHolder(view) } + override fun getItemViewType(position: Int): Int { + return if (position == 0) OFFSET_ITEM else SONG + } + override fun onBindViewHolder(holder: SongAdapter.ViewHolder, position: Int) { if (holder.itemViewType == OFFSET_ITEM) { val color = ThemeStore.accentColor(activity) @@ -54,6 +61,10 @@ class ShuffleButtonSongAdapter( } } else { super.onBindViewHolder(holder, position - 1) + val landscape = RetroUtil.isLandscape() + if ((PreferenceUtil.songGridSize > 2 && !landscape) || (PreferenceUtil.songGridSizeLand > 5 && landscape)) { + holder.menu?.visibility = View.GONE + } } } @@ -69,4 +80,5 @@ class ShuffleButtonSongAdapter( super.onClick(v) } } + } diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/song/SongAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/song/SongAdapter.kt index 0360e3b5..b828f664 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/song/SongAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/song/SongAdapter.kt @@ -30,8 +30,9 @@ import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.show +import code.name.monkey.retromusic.glide.GlideApp +import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.RetroMusicColoredTarget -import code.name.monkey.retromusic.glide.SongGlideRequest import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.SortOrder import code.name.monkey.retromusic.helper.menu.SongMenuHelper @@ -42,7 +43,6 @@ import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import com.afollestad.materialcab.MaterialCab -import com.bumptech.glide.Glide import me.zhanghai.android.fastscroll.PopupTextProvider /** @@ -120,9 +120,8 @@ open class SongAdapter( if (holder.image == null) { return } - SongGlideRequest.Builder.from(Glide.with(activity), song) - .checkIgnoreMediaStore(activity) - .generatePalette(activity).build() + GlideApp.with(activity).asBitmapPalette().songCoverOptions(song) + .load(RetroGlideExtension.getSongModel(song)) .into(object : RetroMusicColoredTarget(holder.image!!) { override fun onColorReady(colors: MediaNotificationProcessor) { setColors(colors, holder) @@ -130,15 +129,15 @@ open class SongAdapter( }) } - private fun getSongTitle(song: Song): String? { + private fun getSongTitle(song: Song): String { return song.title } - private fun getSongText(song: Song): String? { + private fun getSongText(song: Song): String { return song.artistName } - private fun getSongText2(song: Song): String? { + private fun getSongText2(song: Song): String { return song.albumName } @@ -165,6 +164,7 @@ open class SongAdapter( SortOrder.SongSortOrder.SONG_ARTIST -> dataSet[position].artistName SortOrder.SongSortOrder.SONG_YEAR -> return MusicUtil.getYearString(dataSet[position].year) SortOrder.SongSortOrder.COMPOSER -> dataSet[position].composer + SortOrder.SongSortOrder.SONG_ALBUM_ARTIST -> dataSet[position].albumArtist else -> { return "" } diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/DynamicShortcutManager.kt b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/DynamicShortcutManager.kt index 2b74eab3..6582d408 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/DynamicShortcutManager.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/DynamicShortcutManager.kt @@ -24,7 +24,6 @@ import android.os.Build import code.name.monkey.retromusic.appshortcuts.shortcuttype.LastAddedShortcutType import code.name.monkey.retromusic.appshortcuts.shortcuttype.ShuffleAllShortcutType import code.name.monkey.retromusic.appshortcuts.shortcuttype.TopTracksShortcutType -import java.util.* @TargetApi(Build.VERSION_CODES.N_MR1) class DynamicShortcutManager(private val context: Context) { diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.kt index c3aee820..461b3e27 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.kt @@ -27,15 +27,16 @@ import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.retromusic.R import code.name.monkey.retromusic.activities.MainActivity import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget -import code.name.monkey.retromusic.glide.SongGlideRequest +import code.name.monkey.retromusic.glide.GlideApp +import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService.* import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.RetroUtil import com.bumptech.glide.Glide -import com.bumptech.glide.request.animation.GlideAnimation import com.bumptech.glide.request.target.SimpleTarget import com.bumptech.glide.request.target.Target +import com.bumptech.glide.request.transition.Transition class AppWidgetBig : BaseAppWidget() { private var target: Target? = null // for cancellation @@ -158,20 +159,22 @@ class AppWidgetBig : BaseAppWidget() { val appContext = service.applicationContext service.runOnUiThread { if (target != null) { - Glide.clear(target) + Glide.with(service).clear(target) } - target = SongGlideRequest.Builder.from(Glide.with(appContext), song) - .checkIgnoreMediaStore(appContext).asBitmap().build() + target = GlideApp.with(appContext) + .asBitmap() + //.checkIgnoreMediaStore() + .load(RetroGlideExtension.getSongModel(song)) .into(object : SimpleTarget(widgetImageSize, widgetImageSize) { override fun onResourceReady( resource: Bitmap, - glideAnimation: GlideAnimation + transition: Transition? ) { update(resource) } - override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) { - super.onLoadFailed(e, errorDrawable) + override fun onLoadFailed(errorDrawable: Drawable?) { + super.onLoadFailed(errorDrawable) update(null) } diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.kt index 8c6935c0..a0729267 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.kt @@ -27,7 +27,8 @@ import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.retromusic.R import code.name.monkey.retromusic.activities.MainActivity import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget -import code.name.monkey.retromusic.glide.SongGlideRequest +import code.name.monkey.retromusic.glide.GlideApp +import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService.* @@ -35,9 +36,9 @@ import code.name.monkey.retromusic.util.ImageUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.RetroUtil import com.bumptech.glide.Glide -import com.bumptech.glide.request.animation.GlideAnimation import com.bumptech.glide.request.target.SimpleTarget import com.bumptech.glide.request.target.Target +import com.bumptech.glide.request.transition.Transition class AppWidgetCard : BaseAppWidget() { private var target: Target? = null // for cancellation @@ -150,14 +151,15 @@ class AppWidgetCard : BaseAppWidget() { // Load the album cover async and push the update on completion service.runOnUiThread { if (target != null) { - Glide.clear(target) + Glide.with(service).clear(target) } - target = SongGlideRequest.Builder.from(Glide.with(service), song) - .checkIgnoreMediaStore(service).generatePalette(service).build().centerCrop() + target = GlideApp.with(service).asBitmapPalette().songCoverOptions(song) + .load(RetroGlideExtension.getSongModel(song)) + .centerCrop() .into(object : SimpleTarget(imageSize, imageSize) { override fun onResourceReady( resource: BitmapPaletteWrapper, - glideAnimation: GlideAnimation + transition: Transition? ) { val palette = resource.palette update( @@ -171,8 +173,8 @@ class AppWidgetCard : BaseAppWidget() { ) } - override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) { - super.onLoadFailed(e, errorDrawable) + override fun onLoadFailed(errorDrawable: Drawable?) { + super.onLoadFailed(errorDrawable) update(null, MaterialValueHelper.getSecondaryTextColor(service, true)) } diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.kt index 63291abf..3d00ee71 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.kt @@ -28,7 +28,8 @@ import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.retromusic.R import code.name.monkey.retromusic.activities.MainActivity import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget -import code.name.monkey.retromusic.glide.SongGlideRequest +import code.name.monkey.retromusic.glide.GlideApp +import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService.* @@ -36,9 +37,9 @@ import code.name.monkey.retromusic.util.ImageUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.RetroUtil import com.bumptech.glide.Glide -import com.bumptech.glide.request.animation.GlideAnimation import com.bumptech.glide.request.target.SimpleTarget import com.bumptech.glide.request.target.Target +import com.bumptech.glide.request.transition.Transition class AppWidgetClassic : BaseAppWidget() { private var target: Target? = null // for cancellation @@ -120,14 +121,16 @@ class AppWidgetClassic : BaseAppWidget() { val appContext = service.applicationContext service.runOnUiThread { if (target != null) { - Glide.clear(target) + Glide.with(service).clear(target) } - target = SongGlideRequest.Builder.from(Glide.with(service), song) - .checkIgnoreMediaStore(service).generatePalette(service).build().centerCrop() + target = GlideApp.with(service).asBitmapPalette().songCoverOptions(song) + .load(RetroGlideExtension.getSongModel(song)) + //.checkIgnoreMediaStore() + .centerCrop() .into(object : SimpleTarget(imageSize, imageSize) { override fun onResourceReady( resource: BitmapPaletteWrapper, - glideAnimation: GlideAnimation + transition: Transition? ) { val palette = resource.palette update( @@ -143,8 +146,8 @@ class AppWidgetClassic : BaseAppWidget() { ) } - override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) { - super.onLoadFailed(e, errorDrawable) + override fun onLoadFailed(errorDrawable: Drawable?) { + super.onLoadFailed(errorDrawable) update(null, Color.WHITE) } diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.kt index e46d69e6..14795cd3 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.kt @@ -27,16 +27,17 @@ import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.retromusic.R import code.name.monkey.retromusic.activities.MainActivity import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget -import code.name.monkey.retromusic.glide.SongGlideRequest +import code.name.monkey.retromusic.glide.GlideApp +import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService.* import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.RetroUtil import com.bumptech.glide.Glide -import com.bumptech.glide.request.animation.GlideAnimation import com.bumptech.glide.request.target.SimpleTarget import com.bumptech.glide.request.target.Target +import com.bumptech.glide.request.transition.Transition class AppWidgetSmall : BaseAppWidget() { private var target: Target? = null // for cancellation @@ -123,14 +124,16 @@ class AppWidgetSmall : BaseAppWidget() { val appContext = service.applicationContext service.runOnUiThread { if (target != null) { - Glide.clear(target) + Glide.with(service).clear(target) } - target = SongGlideRequest.Builder.from(Glide.with(service), song) - .checkIgnoreMediaStore(service).generatePalette(service).build().centerCrop() + target = GlideApp.with(service).asBitmapPalette().songCoverOptions(song) + //.checkIgnoreMediaStore() + .load(RetroGlideExtension.getSongModel(song)) + .centerCrop() .into(object : SimpleTarget(imageSize, imageSize) { override fun onResourceReady( resource: BitmapPaletteWrapper, - glideAnimation: GlideAnimation + transition: Transition? ) { val palette = resource.palette update( @@ -144,8 +147,8 @@ class AppWidgetSmall : BaseAppWidget() { ) } - override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) { - super.onLoadFailed(e, errorDrawable) + override fun onLoadFailed(errorDrawable: Drawable?) { + super.onLoadFailed(errorDrawable) update(null, MaterialValueHelper.getSecondaryTextColor(service, true)) } diff --git a/app/src/main/java/code/name/monkey/retromusic/auto/AutoMediaIDHelper.java b/app/src/main/java/code/name/monkey/retromusic/auto/AutoMediaIDHelper.java new file mode 100644 index 00000000..e087e7e7 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/auto/AutoMediaIDHelper.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2019 Hemanth Savarala. + * + * 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.auto; + +import androidx.annotation.NonNull; + +/** + * Created by Beesham Sarendranauth (Beesham) + */ +public class AutoMediaIDHelper { + + // Media IDs used on browseable items of MediaBrowser + public static final String MEDIA_ID_EMPTY_ROOT = "__EMPTY_ROOT__"; + public static final String MEDIA_ID_ROOT = "__ROOT__"; + public static final String MEDIA_ID_MUSICS_BY_SEARCH = "__BY_SEARCH__"; // TODO + public static final String MEDIA_ID_MUSICS_BY_HISTORY = "__BY_HISTORY__"; + public static final String MEDIA_ID_MUSICS_BY_TOP_TRACKS = "__BY_TOP_TRACKS__"; + public static final String MEDIA_ID_MUSICS_BY_SUGGESTIONS = "__BY_SUGGESTIONS__"; + public static final String MEDIA_ID_MUSICS_BY_PLAYLIST = "__BY_PLAYLIST__"; + public static final String MEDIA_ID_MUSICS_BY_ALBUM = "__BY_ALBUM__"; + public static final String MEDIA_ID_MUSICS_BY_ARTIST = "__BY_ARTIST__"; + public static final String MEDIA_ID_MUSICS_BY_ALBUM_ARTIST = "__BY_ALBUM_ARTIST__"; + public static final String MEDIA_ID_MUSICS_BY_GENRE = "__BY_GENRE__"; + public static final String MEDIA_ID_MUSICS_BY_SHUFFLE = "__BY_SHUFFLE__"; + public static final String MEDIA_ID_MUSICS_BY_QUEUE = "__BY_QUEUE__"; + + private static final String CATEGORY_SEPARATOR = "__/__"; + private static final String LEAF_SEPARATOR = "__|__"; + + /** + * Create a String value that represents a playable or a browsable media. + *

+ * Encode the media browseable categories, if any, and the unique music ID, if any, + * into a single String mediaID. + *

+ * MediaIDs are of the form __/____|__, to make it + * easy to find the category (like genre) that a music was selected from, so we + * can correctly build the playing queue. This is specially useful when + * one music can appear in more than one list, like "by genre -> genre_1" + * and "by artist -> artist_1". + * + * @param mediaID Unique ID for playable items, or null for browseable items. + * @param categories Hierarchy of categories representing this item's browsing parents. + * @return A hierarchy-aware media ID. + */ + public static String createMediaID(String mediaID, String... categories) { + StringBuilder sb = new StringBuilder(); + if (categories != null) { + for (int i = 0; i < categories.length; i++) { + if (!isValidCategory(categories[i])) { + throw new IllegalArgumentException("Invalid category: " + categories[i]); + } + sb.append(categories[i]); + if (i < categories.length - 1) { + sb.append(CATEGORY_SEPARATOR); + } + } + } + if (mediaID != null) { + sb.append(LEAF_SEPARATOR).append(mediaID); + } + return sb.toString(); + } + + public static String extractCategory(@NonNull String mediaID) { + int pos = mediaID.indexOf(LEAF_SEPARATOR); + if (pos >= 0) { + return mediaID.substring(0, pos); + } + return mediaID; + } + + public static String extractMusicID(@NonNull String mediaID) { + int pos = mediaID.indexOf(LEAF_SEPARATOR); + if (pos >= 0) { + return mediaID.substring(pos + LEAF_SEPARATOR.length()); + } + return null; + } + + public static boolean isBrowseable(@NonNull String mediaID) { + return !mediaID.contains(LEAF_SEPARATOR); + } + + private static boolean isValidCategory(String category) { + return category == null || + (!category.contains(CATEGORY_SEPARATOR) && !category.contains(LEAF_SEPARATOR)); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/auto/AutoMusicProvider.kt b/app/src/main/java/code/name/monkey/retromusic/auto/AutoMusicProvider.kt new file mode 100644 index 00000000..1f4f4d5d --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/auto/AutoMusicProvider.kt @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2019 Hemanth Savarala. + * + * 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.auto + +import android.content.Context +import android.content.res.Resources +import android.net.Uri +import android.support.v4.media.MediaBrowserCompat +import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.helper.MusicPlayerRemote +import code.name.monkey.retromusic.model.CategoryInfo +import code.name.monkey.retromusic.model.Song +import code.name.monkey.retromusic.repository.* +import code.name.monkey.retromusic.service.MusicService +import code.name.monkey.retromusic.util.MusicUtil +import code.name.monkey.retromusic.util.PreferenceUtil +import java.lang.ref.WeakReference +import java.util.* + + +/** + * Created by Beesham Sarendranauth (Beesham) + */ +class AutoMusicProvider( + val mContext: Context, + private val songsRepository: SongRepository, + private val albumsRepository: AlbumRepository, + private val artistsRepository: ArtistRepository, + private val genresRepository: GenreRepository, + private val playlistsRepository: PlaylistRepository, + private val topPlayedRepository: TopPlayedRepository +) { + private var mMusicService: WeakReference? = null + + fun setMusicService(service: MusicService) { + mMusicService = WeakReference(service) + } + + fun getChildren(mediaId: String?, resources: Resources): List { + val mediaItems: MutableList = ArrayList() + when (mediaId) { + AutoMediaIDHelper.MEDIA_ID_ROOT -> { + mediaItems.addAll(getRootChildren(resources)) + } + AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_PLAYLIST -> for (playlist in playlistsRepository.playlists()) { + mediaItems.add( + AutoMediaItem.with(mContext) + .path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_PLAYLIST, playlist.id) + .icon(R.drawable.ic_playlist_play) + .title(playlist.name) + .subTitle(playlist.getInfoString(mContext)) + .asPlayable() + .build() + ) + } + AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM -> for (album in albumsRepository.albums()) { + mediaItems.add( + AutoMediaItem.with(mContext) + .path(mediaId, album.id) + .title(album.title) + .subTitle(album.albumArtist ?: album.artistName) + .icon(MusicUtil.getMediaStoreAlbumCoverUri(album.id)) + .asPlayable() + .build() + ) + } + AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ARTIST -> for (artist in artistsRepository.artists()) { + mediaItems.add( + AutoMediaItem.with(mContext) + .asPlayable() + .path(mediaId, artist.id) + .title(artist.name) + .build() + ) + } + AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM_ARTIST -> for (artist in artistsRepository.albumArtists()) { + mediaItems.add( + AutoMediaItem.with(mContext) + .asPlayable() + // we just pass album id here as we don't have album artist id's + .path(mediaId, artist.safeGetFirstAlbum().id) + .title(artist.name) + .build() + ) + } + AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_GENRE -> for (genre in genresRepository.genres()) { + mediaItems.add( + AutoMediaItem.with(mContext) + .asPlayable() + .path(mediaId, genre.id) + .title(genre.name) + .build() + ) + } + AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_QUEUE -> + mMusicService?.get()?.playingQueue + ?.let { + for (song in it) { + mediaItems.add( + AutoMediaItem.with(mContext) + .asPlayable() + .path(mediaId, song.id) + .title(song.title) + .subTitle(song.artistName) + .icon(MusicUtil.getMediaStoreAlbumCoverUri(song.albumId)) + .build() + ) + } + } + else -> { + getPlaylistChildren(mediaId, mediaItems) + } + } + return mediaItems + } + + private fun getPlaylistChildren( + mediaId: String?, + mediaItems: MutableList + ) { + val songs = when (mediaId) { + AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_TOP_TRACKS -> { + topPlayedRepository.topTracks() + } + AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_HISTORY -> { + topPlayedRepository.recentlyPlayedTracks() + } + AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_SUGGESTIONS -> { + topPlayedRepository.notRecentlyPlayedTracks().take(8) + } + else -> { + emptyList() + } + } + songs.forEach { song -> + mediaItems.add( + getPlayableSong(mediaId, song) + ) + } + } + + private fun getRootChildren(resources: Resources): List { + val mediaItems: MutableList = ArrayList() + val libraryCategories = PreferenceUtil.libraryCategory + libraryCategories.forEach { + if (it.visible) { + when (it.category) { + CategoryInfo.Category.Albums -> { + mediaItems.add( + AutoMediaItem.with(mContext) + .asBrowsable() + .path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM) + .gridLayout(true) + .icon(R.drawable.ic_album) + .title(resources.getString(R.string.albums)).build() + ) + } + CategoryInfo.Category.Artists -> { + if (PreferenceUtil.albumArtistsOnly) { + mediaItems.add( + AutoMediaItem.with(mContext) + .asBrowsable() + .path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM_ARTIST) + .icon(R.drawable.ic_album_artist) + .title(resources.getString(R.string.album_artist)).build() + ) + } else { + mediaItems.add( + AutoMediaItem.with(mContext) + .asBrowsable() + .path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ARTIST) + .icon(R.drawable.ic_artist) + .title(resources.getString(R.string.artists)).build() + ) + } + } + CategoryInfo.Category.Genres -> { + mediaItems.add( + AutoMediaItem.with(mContext) + .asBrowsable() + .path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_GENRE) + .icon(R.drawable.ic_guitar) + .title(resources.getString(R.string.genres)).build() + ) + } + CategoryInfo.Category.Playlists -> { + mediaItems.add( + AutoMediaItem.with(mContext) + .asBrowsable() + .path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_PLAYLIST) + .icon(R.drawable.ic_playlist_play) + .title(resources.getString(R.string.playlists)).build() + ) + } + else -> { + } + } + } + } + mediaItems.add( + AutoMediaItem.with(mContext) + .asPlayable() + .path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_SHUFFLE) + .icon(R.drawable.ic_shuffle) + .title(resources.getString(R.string.action_shuffle_all)) + .subTitle(MusicUtil.getPlaylistInfoString(mContext, songsRepository.songs())) + .build() + ) + mediaItems.add( + AutoMediaItem.with(mContext) + .asBrowsable() + .path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_QUEUE) + .icon(R.drawable.ic_queue_music) + .title(resources.getString(R.string.queue)) + .subTitle(MusicUtil.getPlaylistInfoString(mContext, MusicPlayerRemote.playingQueue)) + .asBrowsable().build() + ) + mediaItems.add( + AutoMediaItem.with(mContext) + .asBrowsable() + .path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_TOP_TRACKS) + .icon(R.drawable.ic_trending_up) + .title(resources.getString(R.string.my_top_tracks)) + .subTitle( + MusicUtil.getPlaylistInfoString( + mContext, + topPlayedRepository.topTracks() + ) + ) + .asBrowsable().build() + ) + mediaItems.add( + AutoMediaItem.with(mContext) + .asBrowsable() + .path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_SUGGESTIONS) + .icon(R.drawable.ic_face) + .title(resources.getString(R.string.suggestion_songs)) + .subTitle( + MusicUtil.getPlaylistInfoString( + mContext, + topPlayedRepository.notRecentlyPlayedTracks().takeIf { + it.size > 9 + } ?: emptyList() + ) + ) + .asBrowsable().build() + ) + mediaItems.add( + AutoMediaItem.with(mContext) + .asBrowsable() + .path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_HISTORY) + .icon(R.drawable.ic_history) + .title(resources.getString(R.string.history)) + .subTitle( + MusicUtil.getPlaylistInfoString( + mContext, + topPlayedRepository.recentlyPlayedTracks() + ) + ) + .asBrowsable().build() + ) + return mediaItems + } + + private fun getPlayableSong(mediaId: String?, song: Song): MediaBrowserCompat.MediaItem { + return AutoMediaItem.with(mContext) + .asPlayable() + .path(mediaId, song.id) + .title(song.title) + .subTitle(song.artistName) + .icon(MusicUtil.getMediaStoreAlbumCoverUri(song.albumId)) + .build() + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/auto/MediaItemBuilder.kt b/app/src/main/java/code/name/monkey/retromusic/auto/MediaItemBuilder.kt new file mode 100644 index 00000000..44f94581 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/auto/MediaItemBuilder.kt @@ -0,0 +1,102 @@ +package code.name.monkey.retromusic.auto + +import android.content.Context +import android.net.Uri +import android.os.Bundle +import android.support.v4.media.MediaBrowserCompat +import android.support.v4.media.MediaDescriptionCompat +import code.name.monkey.retromusic.util.ImageUtil + + +internal object AutoMediaItem { + fun with(context: Context): Builder { + return Builder(context) + } + + internal class Builder(val mContext: Context) { + private var mBuilder: MediaDescriptionCompat.Builder? + private var mFlags = 0 + fun path(fullPath: String): Builder { + mBuilder?.setMediaId(fullPath) + return this + } + + fun path(path: String?, id: Long): Builder { + return path(AutoMediaIDHelper.createMediaID(id.toString(), path)) + } + + fun title(title: String): Builder { + mBuilder?.setTitle(title) + return this + } + + fun subTitle(subTitle: String): Builder { + mBuilder?.setSubtitle(subTitle) + return this + } + + fun icon(uri: Uri?): Builder { + mBuilder?.setIconUri(uri) + return this + } + + fun icon(iconDrawableId: Int): Builder { + mBuilder?.setIconBitmap( + ImageUtil.createBitmap( + ImageUtil.getVectorDrawable( + mContext.resources, + iconDrawableId, + mContext.theme + ) + ) + ) + return this + } + + fun gridLayout(isGrid: Boolean): Builder { + + val hints = Bundle() + hints.putBoolean(CONTENT_STYLE_SUPPORTED, true) + hints.putInt( + CONTENT_STYLE_BROWSABLE_HINT, + if (isGrid) CONTENT_STYLE_GRID_ITEM_HINT_VALUE else CONTENT_STYLE_LIST_ITEM_HINT_VALUE + ) + hints.putInt( + CONTENT_STYLE_PLAYABLE_HINT, + if (isGrid) CONTENT_STYLE_GRID_ITEM_HINT_VALUE else CONTENT_STYLE_LIST_ITEM_HINT_VALUE + ) + mBuilder?.setExtras(hints) + return this + } + + fun asBrowsable(): Builder { + mFlags = mFlags or MediaBrowserCompat.MediaItem.FLAG_BROWSABLE + return this + } + + fun asPlayable(): Builder { + mFlags = mFlags or MediaBrowserCompat.MediaItem.FLAG_PLAYABLE + return this + } + + fun build(): MediaBrowserCompat.MediaItem { + val result = MediaBrowserCompat.MediaItem(mBuilder!!.build(), mFlags) + mBuilder = null + mFlags = 0 + return result + } + + init { + mBuilder = MediaDescriptionCompat.Builder() + } + companion object{ + // Hints - see https://developer.android.com/training/cars/media#default-content-style + const val CONTENT_STYLE_SUPPORTED = "android.media.browse.CONTENT_STYLE_SUPPORTED" + const val CONTENT_STYLE_BROWSABLE_HINT = "android.media.browse.CONTENT_STYLE_BROWSABLE_HINT" + const val CONTENT_STYLE_PLAYABLE_HINT = "android.media.browse.CONTENT_STYLE_PLAYABLE_HINT" + const val CONTENT_STYLE_LIST_ITEM_HINT_VALUE = 1 + const val CONTENT_STYLE_GRID_ITEM_HINT_VALUE = 2 + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/cast/CastHelper.kt b/app/src/main/java/code/name/monkey/retromusic/cast/CastHelper.kt new file mode 100644 index 00000000..2c747a32 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/cast/CastHelper.kt @@ -0,0 +1,83 @@ +package code.name.monkey.retromusic.cast + +import androidx.core.net.toUri +import code.name.monkey.retromusic.cast.RetroWebServer.Companion.MIME_TYPE_AUDIO +import code.name.monkey.retromusic.cast.RetroWebServer.Companion.PART_COVER_ART +import code.name.monkey.retromusic.cast.RetroWebServer.Companion.PART_SONG +import code.name.monkey.retromusic.model.Song +import code.name.monkey.retromusic.util.RetroUtil +import com.google.android.gms.cast.* +import com.google.android.gms.cast.MediaInfo.STREAM_TYPE_BUFFERED +import com.google.android.gms.cast.MediaMetadata.* +import com.google.android.gms.cast.framework.CastSession +import com.google.android.gms.common.images.WebImage +import java.net.MalformedURLException +import java.net.URL + +object CastHelper { + + private const val CAST_MUSIC_METADATA_ID = "metadata_id" + private const val CAST_MUSIC_METADATA_ALBUM_ID = "metadata_album_id" + private const val CAST_URL_PROTOCOL = "http" + + fun castSong(castSession: CastSession, song: Song) { + try { + val remoteMediaClient = castSession.remoteMediaClient + val mediaLoadOptions = MediaLoadOptions.Builder().apply { + setPlayPosition(0) + setAutoplay(true) + }.build() + remoteMediaClient?.load(song.toMediaInfo()!!, mediaLoadOptions) + } catch (e: Exception) { + e.printStackTrace() + } + } + + fun castQueue(castSession: CastSession, songs: List, position: Int, progress: Long) { + try { + val remoteMediaClient = castSession.remoteMediaClient + remoteMediaClient?.queueLoad( + songs.toMediaInfoList(), + position, + MediaStatus.REPEAT_MODE_REPEAT_OFF, + progress, + null + ) + } catch (e: Exception) { + e.printStackTrace() + } + } + + private fun List.toMediaInfoList(): Array { + return map { MediaQueueItem.Builder(it.toMediaInfo()!!).build() }.toTypedArray() + } + + private fun Song.toMediaInfo(): MediaInfo? { + val song = this + val baseUrl: URL + try { + baseUrl = URL(CAST_URL_PROTOCOL, RetroUtil.getIpAddress(true), SERVER_PORT, "") + } catch (e: MalformedURLException) { + return null + } + + val songUrl = "$baseUrl/$PART_SONG?id=${song.id}" + val albumArtUrl = "$baseUrl/$PART_COVER_ART?id=${song.albumId}" + val musicMetadata = MediaMetadata(MEDIA_TYPE_MUSIC_TRACK).apply { + putInt(CAST_MUSIC_METADATA_ID, song.id.toInt()) + putInt(CAST_MUSIC_METADATA_ALBUM_ID, song.albumId.toInt()) + putString(KEY_TITLE, song.title) + putString(KEY_ARTIST, song.artistName) + putString(KEY_ALBUM_TITLE, song.albumName) + putInt(KEY_TRACK_NUMBER, song.trackNumber) + addImage(WebImage(albumArtUrl.toUri())) + } + return MediaInfo.Builder(songUrl).apply { + setStreamType(STREAM_TYPE_BUFFERED) + setContentType(MIME_TYPE_AUDIO) + setMetadata(musicMetadata) + setStreamDuration(song.duration) + }.build() + + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/cast/CastOptionsProvider.kt b/app/src/main/java/code/name/monkey/retromusic/cast/CastOptionsProvider.kt new file mode 100644 index 00000000..a8d14aae --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/cast/CastOptionsProvider.kt @@ -0,0 +1,41 @@ +package code.name.monkey.retromusic.cast + +import android.content.Context +import com.google.android.gms.cast.CastMediaControlIntent +import com.google.android.gms.cast.framework.CastOptions +import com.google.android.gms.cast.framework.OptionsProvider +import com.google.android.gms.cast.framework.SessionProvider +import com.google.android.gms.cast.framework.media.CastMediaOptions +import com.google.android.gms.cast.framework.media.MediaIntentReceiver +import com.google.android.gms.cast.framework.media.NotificationOptions +import java.util.* + + +class CastOptionsProvider : OptionsProvider { + override fun getCastOptions(context: Context): CastOptions { + val buttonActions: MutableList = ArrayList() + buttonActions.add(MediaIntentReceiver.ACTION_SKIP_PREV) + buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK) + buttonActions.add(MediaIntentReceiver.ACTION_SKIP_NEXT) + buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING) + val compatButtonActionsIndices = intArrayOf(1, 3) + val notificationOptions = NotificationOptions.Builder() + .setActions(buttonActions, compatButtonActionsIndices) + .setTargetActivityClassName(ExpandedControlsActivity::class.java.name) + .build() + + val mediaOptions = CastMediaOptions.Builder() + .setNotificationOptions(notificationOptions) + .setExpandedControllerActivityClassName(ExpandedControlsActivity::class.java.name) + .build() + + return CastOptions.Builder() + .setReceiverApplicationId(CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID) + .setCastMediaOptions(mediaOptions) + .build() + } + + override fun getAdditionalSessionProviders(context: Context?): List? { + return null + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/cast/ExpandedControlsActivity.kt b/app/src/main/java/code/name/monkey/retromusic/cast/ExpandedControlsActivity.kt new file mode 100644 index 00000000..1525bdd1 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/cast/ExpandedControlsActivity.kt @@ -0,0 +1,19 @@ +package code.name.monkey.retromusic.cast + + +import android.view.Menu +import code.name.monkey.retromusic.R + +import com.google.android.gms.cast.framework.CastButtonFactory + +import com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity + + +class ExpandedControlsActivity : ExpandedControllerActivity() { + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + super.onCreateOptionsMenu(menu) + menuInflater.inflate(R.menu.menu_cast, menu) + CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.action_cast) + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/cast/RetroSessionManager.kt b/app/src/main/java/code/name/monkey/retromusic/cast/RetroSessionManager.kt new file mode 100644 index 00000000..69bc4534 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/cast/RetroSessionManager.kt @@ -0,0 +1,22 @@ +package code.name.monkey.retromusic.cast + +import com.google.android.gms.cast.framework.CastSession +import com.google.android.gms.cast.framework.SessionManagerListener + +interface RetroSessionManager : SessionManagerListener { + override fun onSessionResuming(p0: CastSession, p1: String) { + + } + + override fun onSessionStartFailed(p0: CastSession, p1: Int) { + + } + + override fun onSessionResumeFailed(p0: CastSession, p1: Int) { + + } + + override fun onSessionEnding(p0: CastSession) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/cast/RetroWebServer.kt b/app/src/main/java/code/name/monkey/retromusic/cast/RetroWebServer.kt new file mode 100644 index 00000000..e460f48b --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/cast/RetroWebServer.kt @@ -0,0 +1,140 @@ +package code.name.monkey.retromusic.cast + +import android.content.Context +import code.name.monkey.retromusic.util.MusicUtil +import fi.iki.elonen.NanoHTTPD +import fi.iki.elonen.NanoHTTPD.Response.Status +import java.io.* + + +const val SERVER_PORT = 9090 + +class RetroWebServer(val context: Context) : NanoHTTPD(SERVER_PORT) { + companion object { + private const val MIME_TYPE_IMAGE = "image/jpg" + const val MIME_TYPE_AUDIO = "audio/mp3" + + const val PART_COVER_ART = "coverart" + const val PART_SONG = "song" + const val PARAM_ID = "id" + var mRetroWebServer: RetroWebServer? = null + fun getInstance(context: Context): RetroWebServer { + if (mRetroWebServer == null) { + mRetroWebServer = RetroWebServer(context) + } + return mRetroWebServer!! + } + } + + override fun serve( + uri: String?, + method: Method?, + headers: MutableMap?, + parms: MutableMap?, + files: MutableMap? + ): Response { + if (uri?.contains(PART_COVER_ART) == true) { + val albumId = parms?.get(PARAM_ID) ?: return errorResponse() + val albumArtUri = MusicUtil.getMediaStoreAlbumCoverUri(albumId.toLong()) + val fis: InputStream? + try { + fis = context.contentResolver.openInputStream(albumArtUri) + } catch (e: FileNotFoundException) { + return errorResponse() + } + return newChunkedResponse(Status.OK, MIME_TYPE_IMAGE, fis) + } else if (uri?.contains(PART_SONG) == true) { + val songId = parms?.get(PARAM_ID) ?: return errorResponse() + val songUri = MusicUtil.getSongFileUri(songId.toLong()) + val songPath = MusicUtil.getSongFilePath(context, songUri) + val song = File(songPath) + return serveFile(headers!!, song, MIME_TYPE_AUDIO) + } + return newFixedLengthResponse(Status.NOT_FOUND, MIME_PLAINTEXT, "Not Found") + } + + private fun serveFile( + header: MutableMap, file: File, + mime: String + ): Response { + var res: Response + try { + // Support (simple) skipping: + var startFrom: Long = 0 + var endAt: Long = -1 + // The value of header range will be bytes=0-1024 something like this + // We get the value of from Bytes i.e. startFrom and toBytes i.e. endAt below + var range = header["range"] + if (range != null) { + if (range.startsWith("bytes=")) { + range = range.substring("bytes=".length) + val minus = range.indexOf('-') + try { + if (minus > 0) { + startFrom = range + .substring(0, minus).toLong() + endAt = range.substring(minus + 1).toLong() + } + } catch (ignored: NumberFormatException) { + } + } + } + + // Chunked Response is used when serving audio file + // Change return code and add Content-Range header when skipping is + // requested + val fileLen = file.length() + if (range != null && startFrom >= 0) { + if (startFrom >= fileLen) { + res = newFixedLengthResponse( + Status.RANGE_NOT_SATISFIABLE, + MIME_PLAINTEXT, "" + ) + res.addHeader("Content-Range", "bytes 0-0/$fileLen") + } else { + if (endAt < 0) { + endAt = fileLen - 1 + } + var newLen = endAt - startFrom + 1 + if (newLen < 0) { + newLen = 0 + } + val dataLen = newLen + val fis: FileInputStream = object : FileInputStream(file) { + @Throws(IOException::class) + override fun available(): Int { + return dataLen.toInt() + } + } + fis.skip(startFrom) + res = newChunkedResponse( + Status.PARTIAL_CONTENT, mime, + fis + ) + res.addHeader("Content-Length", "" + dataLen) + res.addHeader( + "Content-Range", "bytes " + startFrom + "-" + + endAt + "/" + fileLen + ) + } + } else { + res = newFixedLengthResponse( + Status.OK, mime, + FileInputStream(file), file.length() + ) + res.addHeader("Accept-Ranges", "bytes") + res.addHeader("Content-Length", "" + fileLen) + } + } catch (ioe: IOException) { + res = newFixedLengthResponse( + Status.FORBIDDEN, + MIME_PLAINTEXT, "FORBIDDEN: Reading file failed." + ) + } + return res + } + + private fun errorResponse(message: String = "Error Occurred"): Response { + return newFixedLengthResponse(Status.INTERNAL_ERROR, MIME_PLAINTEXT, message) + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/db/PlaylistDao.kt b/app/src/main/java/code/name/monkey/retromusic/db/PlaylistDao.kt index 579777b9..fa39a5cb 100644 --- a/app/src/main/java/code/name/monkey/retromusic/db/PlaylistDao.kt +++ b/app/src/main/java/code/name/monkey/retromusic/db/PlaylistDao.kt @@ -47,7 +47,7 @@ interface PlaylistDao { @Query("SELECT * FROM SongEntity WHERE playlist_creator_id = :playlistId AND id = :songId") suspend fun isSongExistsInPlaylist(playlistId: Long, songId: Long): List - @Query("SELECT * FROM SongEntity WHERE playlist_creator_id = :playlistId") + @Query("SELECT * FROM SongEntity WHERE playlist_creator_id = :playlistId ORDER BY song_key asc") fun songsFromPlaylist(playlistId: Long): LiveData> @Delete diff --git a/app/src/main/java/code/name/monkey/retromusic/db/PlaylistEntity.kt b/app/src/main/java/code/name/monkey/retromusic/db/PlaylistEntity.kt index cda8bff9..5493b115 100644 --- a/app/src/main/java/code/name/monkey/retromusic/db/PlaylistEntity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/db/PlaylistEntity.kt @@ -18,7 +18,7 @@ import android.os.Parcelable import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Entity @Parcelize diff --git a/app/src/main/java/code/name/monkey/retromusic/db/PlaylistWithSongs.kt b/app/src/main/java/code/name/monkey/retromusic/db/PlaylistWithSongs.kt index 63c82e39..da80d822 100644 --- a/app/src/main/java/code/name/monkey/retromusic/db/PlaylistWithSongs.kt +++ b/app/src/main/java/code/name/monkey/retromusic/db/PlaylistWithSongs.kt @@ -17,7 +17,7 @@ package code.name.monkey.retromusic.db import android.os.Parcelable import androidx.room.Embedded import androidx.room.Relation -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize data class PlaylistWithSongs( diff --git a/app/src/main/java/code/name/monkey/retromusic/db/SongEntity.kt b/app/src/main/java/code/name/monkey/retromusic/db/SongEntity.kt index 27946028..206c91e2 100644 --- a/app/src/main/java/code/name/monkey/retromusic/db/SongEntity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/db/SongEntity.kt @@ -19,7 +19,7 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.Index import androidx.room.PrimaryKey -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize @Entity(indices = [Index(value = ["playlist_creator_id", "id"], unique = true)]) diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/BlacklistFolderChooserDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/BlacklistFolderChooserDialog.java deleted file mode 100644 index 3a15c2d8..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/BlacklistFolderChooserDialog.java +++ /dev/null @@ -1,155 +0,0 @@ -package code.name.monkey.retromusic.dialogs; - -import android.Manifest; -import android.app.Dialog; -import android.content.pm.PackageManager; -import android.os.Build; -import android.os.Bundle; -import android.os.Environment; -import android.view.View; -import androidx.annotation.NonNull; -import androidx.core.app.ActivityCompat; -import androidx.fragment.app.DialogFragment; -import code.name.monkey.retromusic.R; -import com.afollestad.materialdialogs.MaterialDialog; -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -public class BlacklistFolderChooserDialog extends DialogFragment - implements MaterialDialog.ListCallback { - - String initialPath = Environment.getExternalStorageDirectory().getAbsolutePath(); - private File parentFolder; - private File[] parentContents; - private boolean canGoUp = false; - private FolderCallback callback; - - public static BlacklistFolderChooserDialog create() { - return new BlacklistFolderChooserDialog(); - } - - private String[] getContentsArray() { - if (parentContents == null) { - if (canGoUp) { - return new String[] {".."}; - } - return new String[] {}; - } - String[] results = new String[parentContents.length + (canGoUp ? 1 : 0)]; - if (canGoUp) { - results[0] = ".."; - } - for (int i = 0; i < parentContents.length; i++) { - results[canGoUp ? i + 1 : i] = parentContents[i].getName(); - } - return results; - } - - private File[] listFiles() { - File[] contents = parentFolder.listFiles(); - List results = new ArrayList<>(); - if (contents != null) { - for (File fi : contents) { - if (fi.isDirectory()) { - results.add(fi); - } - } - Collections.sort(results, new FolderSorter()); - return results.toArray(new File[results.size()]); - } - return null; - } - - @NonNull - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - && ActivityCompat.checkSelfPermission( - requireActivity(), Manifest.permission.READ_EXTERNAL_STORAGE) - != PackageManager.PERMISSION_GRANTED) { - return new MaterialDialog.Builder(requireActivity()) - .title(R.string.md_error_label) - .content(R.string.md_storage_perm_error) - .positiveText(android.R.string.ok) - .build(); - } - if (savedInstanceState == null) { - savedInstanceState = new Bundle(); - } - if (!savedInstanceState.containsKey("current_path")) { - savedInstanceState.putString("current_path", initialPath); - } - parentFolder = new File(savedInstanceState.getString("current_path", File.pathSeparator)); - checkIfCanGoUp(); - parentContents = listFiles(); - MaterialDialog.Builder builder = - new MaterialDialog.Builder(requireContext()) - .title(parentFolder.getAbsolutePath()) - .items((CharSequence[]) getContentsArray()) - .itemsCallback(this) - .autoDismiss(false) - .onPositive( - (dialog, which) -> { - callback.onFolderSelection(BlacklistFolderChooserDialog.this, parentFolder); - dismiss(); - }) - .onNegative((materialDialog, dialogAction) -> dismiss()) - .positiveText(R.string.add_action) - .negativeText(android.R.string.cancel); - return builder.build(); - } - - @Override - public void onSelection(MaterialDialog materialDialog, View view, int i, CharSequence s) { - if (canGoUp && i == 0) { - parentFolder = parentFolder.getParentFile(); - if (parentFolder.getAbsolutePath().equals("/storage/emulated")) { - parentFolder = parentFolder.getParentFile(); - } - checkIfCanGoUp(); - } else { - parentFolder = parentContents[canGoUp ? i - 1 : i]; - canGoUp = true; - if (parentFolder.getAbsolutePath().equals("/storage/emulated")) { - parentFolder = Environment.getExternalStorageDirectory(); - } - } - reload(); - } - - private void checkIfCanGoUp() { - canGoUp = parentFolder.getParent() != null; - } - - private void reload() { - parentContents = listFiles(); - MaterialDialog dialog = (MaterialDialog) getDialog(); - dialog.setTitle(parentFolder.getAbsolutePath()); - dialog.setItems((CharSequence[]) getContentsArray()); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putString("current_path", parentFolder.getAbsolutePath()); - } - - public void setCallback(FolderCallback callback) { - this.callback = callback; - } - - public interface FolderCallback { - void onFolderSelection(@NonNull BlacklistFolderChooserDialog dialog, @NonNull File folder); - } - - private static class FolderSorter implements Comparator { - - @Override - public int compare(File lhs, File rhs) { - return lhs.getName().compareTo(rhs.getName()); - } - } -} diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/BlacklistFolderChooserDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/BlacklistFolderChooserDialog.kt new file mode 100644 index 00000000..76abc0c6 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/BlacklistFolderChooserDialog.kt @@ -0,0 +1,152 @@ +package code.name.monkey.retromusic.dialogs + +import android.Manifest +import android.app.Dialog +import android.content.pm.PackageManager +import android.os.Build +import android.os.Bundle +import android.os.Environment +import androidx.core.app.ActivityCompat +import androidx.fragment.app.DialogFragment +import code.name.monkey.retromusic.R +import com.afollestad.materialdialogs.MaterialDialog +import com.afollestad.materialdialogs.list.listItems +import com.afollestad.materialdialogs.list.updateListItems +import java.io.File +import java.util.* + +class BlacklistFolderChooserDialog : DialogFragment() { + private var initialPath: String = Environment.getExternalStorageDirectory().absolutePath + private var parentFolder: File? = null + private var parentContents: Array? = null + private var canGoUp = false + private var callback: FolderCallback? = null + private val contentsArray: Array + get() { + if (parentContents == null) { + return if (canGoUp) { + arrayOf("..") + } else arrayOf() + } + val results = arrayOfNulls(parentContents!!.size + if (canGoUp) 1 else 0) + if (canGoUp) { + results[0] = ".." + } + for (i in parentContents!!.indices) { + results[if (canGoUp) i + 1 else i] = parentContents!![i].name + } + return results + } + + private fun listFiles(): Array? { + val contents = parentFolder!!.listFiles() + val results: MutableList = ArrayList() + if (contents != null) { + for (fi in contents) { + if (fi.isDirectory) { + results.add(fi) + } + } + Collections.sort(results, FolderSorter()) + return results.toTypedArray() + } + return null + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + var mSavedInstanceState = savedInstanceState + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + && ActivityCompat.checkSelfPermission( + requireActivity(), Manifest.permission.READ_EXTERNAL_STORAGE + ) + != PackageManager.PERMISSION_GRANTED + ) { + return MaterialDialog(requireActivity()).show { + title(res = R.string.md_error_label) + message(res = R.string.md_storage_perm_error) + positiveButton(res = android.R.string.ok) + } + } + if (mSavedInstanceState == null) { + mSavedInstanceState = Bundle() + } + if (!mSavedInstanceState.containsKey("current_path")) { + mSavedInstanceState.putString("current_path", initialPath) + } + parentFolder = File(mSavedInstanceState.getString("current_path", File.pathSeparator)) + checkIfCanGoUp() + parentContents = listFiles() + return MaterialDialog(requireContext()) + .title(text = parentFolder!!.absolutePath) + .listItems( + items = contentsArray.toCharSequence(), + waitForPositiveButton = false + ) { _: MaterialDialog, i: Int, _: CharSequence -> + onSelection(i) + } + .noAutoDismiss() + .cornerRadius(literalDp = 20F) + .positiveButton(res = R.string.add_action) { + callback!!.onFolderSelection(this@BlacklistFolderChooserDialog, parentFolder!!) + dismiss() + } + .negativeButton(res = android.R.string.cancel) { dismiss() } + } + + private fun onSelection(i: Int) { + if (canGoUp && i == 0) { + parentFolder = parentFolder!!.parentFile + if (parentFolder!!.absolutePath == "/storage/emulated") { + parentFolder = parentFolder!!.parentFile + } + checkIfCanGoUp() + } else { + parentFolder = parentContents!![if (canGoUp) i - 1 else i] + canGoUp = true + if (parentFolder!!.absolutePath == "/storage/emulated") { + parentFolder = Environment.getExternalStorageDirectory() + } + } + reload() + } + + private fun checkIfCanGoUp() { + canGoUp = parentFolder!!.parent != null + } + + private fun reload() { + parentContents = listFiles() + val dialog = dialog as MaterialDialog? + dialog!!.setTitle(parentFolder!!.absolutePath) + dialog.updateListItems(items = contentsArray.toCharSequence()) + } + + private fun Array.toCharSequence(): List { + return map { it as CharSequence } + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putString("current_path", parentFolder!!.absolutePath) + } + + fun setCallback(callback: FolderCallback?) { + this.callback = callback + } + + interface FolderCallback { + fun onFolderSelection(dialog: BlacklistFolderChooserDialog, folder: File) + } + + private class FolderSorter : Comparator { + override fun compare(lhs: File, rhs: File): Int { + return lhs.name.compareTo(rhs.name) + } + } + + companion object { + fun create(): BlacklistFolderChooserDialog { + return BlacklistFolderChooserDialog() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.kt index b4bcf846..7792fd46 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.kt @@ -17,29 +17,23 @@ package code.name.monkey.retromusic.dialogs import android.app.Dialog import android.os.Bundle import android.text.TextUtils -import android.view.LayoutInflater -import android.widget.Toast import androidx.core.os.bundleOf import androidx.fragment.app.DialogFragment -import androidx.lifecycle.lifecycleScope import code.name.monkey.retromusic.EXTRA_SONG import code.name.monkey.retromusic.R -import code.name.monkey.retromusic.db.PlaylistEntity -import code.name.monkey.retromusic.db.toSongEntity +import code.name.monkey.retromusic.databinding.DialogPlaylistBinding import code.name.monkey.retromusic.extensions.colorButtons import code.name.monkey.retromusic.extensions.extra import code.name.monkey.retromusic.extensions.materialDialog import code.name.monkey.retromusic.fragments.LibraryViewModel -import code.name.monkey.retromusic.fragments.ReloadType.Playlists import code.name.monkey.retromusic.model.Song import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputLayout -import kotlinx.android.synthetic.main.dialog_playlist.view.* -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import org.koin.androidx.viewmodel.ext.android.sharedViewModel class CreatePlaylistDialog : DialogFragment() { + private var _binding: DialogPlaylistBinding? = null + private val binding get() = _binding!! private val libraryViewModel by sharedViewModel() companion object { @@ -56,13 +50,15 @@ class CreatePlaylistDialog : DialogFragment() { } } + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val view = LayoutInflater.from(requireActivity()).inflate(R.layout.dialog_playlist, null) + _binding = DialogPlaylistBinding.inflate(layoutInflater) + val songs: List = extra>(EXTRA_SONG).value ?: emptyList() - val playlistView: TextInputEditText = view.actionNewPlaylist - val playlistContainer: TextInputLayout = view.actionNewPlaylistContainer + val playlistView: TextInputEditText = binding.actionNewPlaylist + val playlistContainer: TextInputLayout = binding.actionNewPlaylistContainer return materialDialog(R.string.new_playlist_title) - .setView(view) + .setView(binding.root) .setPositiveButton( R.string.create_action ) { _, _ -> @@ -77,4 +73,9 @@ class CreatePlaylistDialog : DialogFragment() { .create() .colorButtons() } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.kt index ca562dca..1a528412 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.kt @@ -14,24 +14,31 @@ */ package code.name.monkey.retromusic.dialogs +import android.app.Activity import android.app.Dialog +import android.content.Intent import android.os.Bundle import androidx.core.os.bundleOf import androidx.core.text.HtmlCompat import androidx.fragment.app.DialogFragment import code.name.monkey.retromusic.EXTRA_SONG import code.name.monkey.retromusic.R -import code.name.monkey.retromusic.extensions.colorButtons +import code.name.monkey.retromusic.activities.saf.SAFGuideActivity import code.name.monkey.retromusic.extensions.extraNotNull -import code.name.monkey.retromusic.extensions.materialDialog import code.name.monkey.retromusic.fragments.LibraryViewModel +import code.name.monkey.retromusic.fragments.ReloadType import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.MusicUtil -import org.koin.androidx.viewmodel.ext.android.sharedViewModel +import code.name.monkey.retromusic.util.SAFUtil +import com.afollestad.materialdialogs.MaterialDialog +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.koin.androidx.viewmodel.ext.android.getViewModel class DeleteSongsDialog : DialogFragment() { - private val libraryViewModel by sharedViewModel() + lateinit var libraryViewModel: LibraryViewModel companion object { fun create(song: Song): DeleteSongsDialog { @@ -50,6 +57,7 @@ class DeleteSongsDialog : DialogFragment() { } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + libraryViewModel = activity?.getViewModel() as LibraryViewModel val songs = extraNotNull>(EXTRA_SONG).value val pair = if (songs.size > 1) { Pair( @@ -69,17 +77,66 @@ class DeleteSongsDialog : DialogFragment() { ) } - return materialDialog(pair.first) - .setMessage(pair.second) - .setCancelable(false) - .setPositiveButton(R.string.action_delete) { _, _ -> - if (songs.isNotEmpty() and (songs.size == 1) and MusicPlayerRemote.isPlaying(songs.first())) { + return MaterialDialog(requireContext()) + .title(pair.first) + .message(text = pair.second) + .noAutoDismiss() + .cornerRadius(16F) + .negativeButton(android.R.string.cancel) { + dismiss() + } + .positiveButton(R.string.action_delete) { + if ((songs.size == 1) && MusicPlayerRemote.isPlaying(songs[0])) { MusicPlayerRemote.playNextSong() } - MusicUtil.deleteTracks(requireActivity(), songs) - libraryViewModel.deleteTracks(songs) + if (!SAFUtil.isSAFRequiredForSongs(songs)) { + CoroutineScope(Dispatchers.IO).launch { + dismiss() + MusicUtil.deleteTracks(requireContext(), songs) + reloadTabs() + } + } else { + if (SAFUtil.isSDCardAccessGranted(requireActivity())) { + deleteSongs(songs) + } else { + startActivityForResult( + Intent(requireActivity(), SAFGuideActivity::class.java), + SAFGuideActivity.REQUEST_CODE_SAF_GUIDE + ) + } + } } - .create() - .colorButtons() + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + when (requestCode) { + SAFGuideActivity.REQUEST_CODE_SAF_GUIDE -> { + SAFUtil.openTreePicker(this) + } + SAFUtil.REQUEST_SAF_PICK_TREE, + SAFUtil.REQUEST_SAF_PICK_FILE -> { + if (resultCode == Activity.RESULT_OK) { + SAFUtil.saveTreeUri(requireActivity(), data) + val songs = extraNotNull>(EXTRA_SONG).value + deleteSongs(songs) + } + } + } + } + + fun deleteSongs(songs: List) { + CoroutineScope(Dispatchers.IO).launch { + dismiss() + MusicUtil.deleteTracks(requireActivity(), songs, null, null) + reloadTabs() + } + } + + fun reloadTabs() { + libraryViewModel.forceReload(ReloadType.Songs) + libraryViewModel.forceReload(ReloadType.HomeSections) + libraryViewModel.forceReload(ReloadType.Artists) + libraryViewModel.forceReload(ReloadType.Albums) } } diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/SleepTimerDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/SleepTimerDialog.kt index 7e671ebd..e454d4a6 100755 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/SleepTimerDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/SleepTimerDialog.kt @@ -38,8 +38,9 @@ import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService.ACTION_PENDING_QUIT import code.name.monkey.retromusic.service.MusicService.ACTION_QUIT import code.name.monkey.retromusic.util.PreferenceUtil -import com.afollestad.materialdialogs.DialogAction import com.afollestad.materialdialogs.MaterialDialog +import com.afollestad.materialdialogs.WhichButton +import com.afollestad.materialdialogs.actions.getActionButton class SleepTimerDialog : DialogFragment() { @@ -150,14 +151,14 @@ class SleepTimerDialog : DialogFragment() { private fun updateCancelButton() { val musicService = MusicPlayerRemote.musicService if (musicService != null && musicService.pendingQuit) { - dialog.getActionButton(DialogAction.NEUTRAL).text = + dialog.getActionButton(WhichButton.NEUTRAL).text = dialog.context.getString(R.string.cancel_current_timer) } else { - dialog.getActionButton(DialogAction.NEUTRAL).text = null + dialog.getActionButton(WhichButton.NEUTRAL).text = null } } - private inner class TimerUpdater() : + private inner class TimerUpdater : CountDownTimer( PreferenceUtil.nextSleepTimerElapsedRealTime - SystemClock.elapsedRealtime(), 1000 diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/SongDetailDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/SongDetailDialog.kt index 0e7c0aba..c363f8b4 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/SongDetailDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/SongDetailDialog.kt @@ -32,13 +32,13 @@ import code.name.monkey.retromusic.extensions.colorButtons import code.name.monkey.retromusic.extensions.materialDialog import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.MusicUtil -import java.io.File -import java.io.IOException import org.jaudiotagger.audio.AudioFileIO import org.jaudiotagger.audio.exceptions.CannotReadException import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException import org.jaudiotagger.audio.exceptions.ReadOnlyFileException import org.jaudiotagger.tag.TagException +import java.io.File +import java.io.IOException class SongDetailDialog : DialogFragment() { diff --git a/app/src/main/java/code/name/monkey/retromusic/extensions/ColorExt.kt b/app/src/main/java/code/name/monkey/retromusic/extensions/ColorExt.kt index 0e064b4a..c15a821a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/extensions/ColorExt.kt +++ b/app/src/main/java/code/name/monkey/retromusic/extensions/ColorExt.kt @@ -41,7 +41,6 @@ import com.google.android.material.button.MaterialButton import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.progressindicator.CircularProgressIndicator -import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputLayout fun Int.ripAlpha(): Int { diff --git a/app/src/main/java/code/name/monkey/retromusic/extensions/DrawableExt.kt b/app/src/main/java/code/name/monkey/retromusic/extensions/DrawableExt.kt index 03e0ad57..9a7c76f9 100644 --- a/app/src/main/java/code/name/monkey/retromusic/extensions/DrawableExt.kt +++ b/app/src/main/java/code/name/monkey/retromusic/extensions/DrawableExt.kt @@ -18,14 +18,10 @@ import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.Canvas -import android.graphics.drawable.AdaptiveIconDrawable import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable -import android.os.Build import androidx.annotation.DimenRes import androidx.annotation.DrawableRes -import androidx.core.content.ContextCompat -import code.name.monkey.retromusic.R fun Context.scaledDrawableResources( @DrawableRes id: Int, diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/DetailListFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/DetailListFragment.kt index af27fedc..b001c1e8 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/DetailListFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/DetailListFragment.kt @@ -17,6 +17,7 @@ package code.name.monkey.retromusic.fragments import android.os.Bundle import android.view.View import androidx.core.os.bundleOf +import androidx.core.view.doOnPreDraw import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs @@ -28,26 +29,48 @@ import code.name.monkey.retromusic.adapter.album.AlbumAdapter import code.name.monkey.retromusic.adapter.artist.ArtistAdapter import code.name.monkey.retromusic.adapter.song.ShuffleButtonSongAdapter import code.name.monkey.retromusic.adapter.song.SongAdapter +import code.name.monkey.retromusic.databinding.FragmentPlaylistDetailBinding import code.name.monkey.retromusic.db.toSong import code.name.monkey.retromusic.extensions.dipToPix -import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment import code.name.monkey.retromusic.interfaces.IAlbumClickListener import code.name.monkey.retromusic.interfaces.IArtistClickListener import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.util.RetroUtil -import kotlinx.android.synthetic.main.fragment_playlist_detail.* +import com.google.android.material.transition.MaterialSharedAxis class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_detail), IArtistClickListener, IAlbumClickListener { private val args by navArgs() + private var _binding: FragmentPlaylistDetailBinding? = null + private val binding get() = _binding!! + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + _binding = FragmentPlaylistDetailBinding.bind(view) + when (args.type) { + TOP_ARTISTS, + RECENT_ARTISTS, + TOP_ALBUMS, + RECENT_ALBUMS, + FAVOURITES -> { + enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) + returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) + } + else -> { + enterTransition = MaterialSharedAxis(MaterialSharedAxis.Y, true) + returnTransition = MaterialSharedAxis(MaterialSharedAxis.Y, false) + } + } + postponeEnterTransition() + view.doOnPreDraw { startPostponedEnterTransition() } + } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - mainActivity.setBottomBarVisibility(false) - mainActivity.setSupportActionBar(toolbar) - progressIndicator.hide() + mainActivity.setSupportActionBar(binding.toolbar) + binding.progressIndicator.hide() when (args.type) { TOP_ARTISTS -> loadArtists(R.string.top_artists, TOP_ARTISTS) RECENT_ARTISTS -> loadArtists(R.string.recent_artists, RECENT_ARTISTS) @@ -59,23 +82,23 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de TOP_PLAYED_PLAYLIST -> topPlayed() } - recyclerView.adapter?.registerAdapterDataObserver(object : AdapterDataObserver() { + binding.recyclerView.adapter?.registerAdapterDataObserver(object : AdapterDataObserver() { override fun onChanged() { super.onChanged() val height = dipToPix(52f) - recyclerView.setPadding(0, 0, 0, height.toInt()) + binding.recyclerView.setPadding(0, 0, 0, height.toInt()) } }) } private fun lastAddedSongs() { - toolbar.setTitle(R.string.last_added) + binding.toolbar.setTitle(R.string.last_added) val songAdapter = ShuffleButtonSongAdapter( requireActivity(), mutableListOf(), R.layout.item_list, null ) - recyclerView.apply { + binding.recyclerView.apply { adapter = songAdapter layoutManager = linearLayoutManager() } @@ -85,13 +108,13 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de } private fun topPlayed() { - toolbar.setTitle(R.string.my_top_tracks) + binding.toolbar.setTitle(R.string.my_top_tracks) val songAdapter = ShuffleButtonSongAdapter( requireActivity(), mutableListOf(), R.layout.item_list, null ) - recyclerView.apply { + binding.recyclerView.apply { adapter = songAdapter layoutManager = linearLayoutManager() } @@ -101,14 +124,14 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de } private fun loadHistory() { - toolbar.setTitle(R.string.history) + binding.toolbar.setTitle(R.string.history) val songAdapter = ShuffleButtonSongAdapter( requireActivity(), mutableListOf(), R.layout.item_list, null ) - recyclerView.apply { + binding.recyclerView.apply { adapter = songAdapter layoutManager = linearLayoutManager() } @@ -118,13 +141,13 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de } private fun loadFavorite() { - toolbar.setTitle(R.string.favorites) + binding.toolbar.setTitle(R.string.favorites) val songAdapter = SongAdapter( requireActivity(), mutableListOf(), R.layout.item_list, null ) - recyclerView.apply { + binding.recyclerView.apply { adapter = songAdapter layoutManager = linearLayoutManager() } @@ -135,9 +158,9 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de } private fun loadArtists(title: Int, type: Int) { - toolbar.setTitle(title) + binding.toolbar.setTitle(title) libraryViewModel.artists(type).observe(viewLifecycleOwner, { artists -> - recyclerView.apply { + binding.recyclerView.apply { adapter = artistAdapter(artists) layoutManager = gridLayoutManager() } @@ -145,9 +168,9 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de } private fun loadAlbums(title: Int, type: Int) { - toolbar.setTitle(title) + binding.toolbar.setTitle(title) libraryViewModel.albums(type).observe(viewLifecycleOwner, { albums -> - recyclerView.apply { + binding.recyclerView.apply { adapter = albumAdapter(albums) layoutManager = gridLayoutManager() } @@ -186,7 +209,7 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de R.id.artistDetailsFragment, bundleOf(EXTRA_ARTIST_ID to artistId), null, - FragmentNavigatorExtras(view to "artist") + FragmentNavigatorExtras(view to artistId.toString()) ) } @@ -196,8 +219,13 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de bundleOf(EXTRA_ALBUM_ID to albumId), null, FragmentNavigatorExtras( - view to "album" + view to albumId.toString() ) ) } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/LibraryViewModel.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/LibraryViewModel.kt index 2a4bcb81..c474823f 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/LibraryViewModel.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/LibraryViewModel.kt @@ -25,9 +25,7 @@ import code.name.monkey.retromusic.model.* import code.name.monkey.retromusic.repository.RealRepository import code.name.monkey.retromusic.util.PreferenceUtil import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext class LibraryViewModel( private val repository: RealRepository @@ -129,16 +127,16 @@ class LibraryViewModel( } } - private fun fetchHomeSections() { + fun fetchHomeSections() { viewModelScope.launch(IO) { home.postValue(repository.homeSections()) } } - fun search(query: String?) { + fun search(query: String?, filters: List) { viewModelScope.launch(IO) { - val result = repository.search(query) - withContext(Main) { searchResults.postValue(result) } + val result = repository.search(query, filters) + searchResults.postValue(result) } } @@ -190,6 +188,10 @@ class LibraryViewModel( println("onShuffleModeChanged") } + override fun onFavoriteStateChanged() { + println("onFavoriteStateChanged") + } + fun shuffleSongs() = viewModelScope.launch(IO) { val songs = repository.allSongs() MusicPlayerRemote.openAndShuffleQueue( diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/MiniPlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/MiniPlayerFragment.kt index 399f5ed1..c37bc346 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/MiniPlayerFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/MiniPlayerFragment.kt @@ -26,23 +26,26 @@ import android.view.MotionEvent import android.view.View import android.view.animation.DecelerateInterpolator import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentMiniPlayerBinding import code.name.monkey.retromusic.extensions.accentColor -import code.name.monkey.retromusic.extensions.applyColor import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.extensions.textColorPrimary import code.name.monkey.retromusic.extensions.textColorSecondary import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment +import code.name.monkey.retromusic.glide.GlideApp +import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.RetroUtil import kotlin.math.abs -import kotlinx.android.synthetic.main.fragment_mini_player.* open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_player), MusicProgressViewUpdateHelper.Callback, View.OnClickListener { + private var _binding: FragmentMiniPlayerBinding? = null + private val binding get() = _binding!! private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper override fun onCreate(savedInstanceState: Bundle?) { @@ -59,38 +62,38 @@ open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_p override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentMiniPlayerBinding.bind(view) view.setOnTouchListener(FlingPlayBackController(requireContext())) setUpMiniPlayer() if (RetroUtil.isTablet()) { - actionNext.show() - actionPrevious.show() - actionNext?.show() - actionPrevious?.show() + binding.actionNext.show() + binding.actionPrevious.show() } else { - actionNext.visibility = if (PreferenceUtil.isExtraControls) View.VISIBLE else View.GONE - actionPrevious.visibility = + binding.actionNext.visibility = + if (PreferenceUtil.isExtraControls) View.VISIBLE else View.GONE + binding.actionPrevious.visibility = if (PreferenceUtil.isExtraControls) View.VISIBLE else View.GONE } - actionNext.setOnClickListener(this) - actionPrevious.setOnClickListener(this) - actionNext?.setOnClickListener(this) - actionPrevious?.setOnClickListener(this) + binding.actionNext.setOnClickListener(this) + binding.actionPrevious.setOnClickListener(this) } private fun setUpMiniPlayer() { setUpPlayPauseButton() - progressBar.accentColor() + binding.progressBar.accentColor() } private fun setUpPlayPauseButton() { - miniPlayerPlayPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) + binding.miniPlayerPlayPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) } private fun updateSongTitle() { - val builder = SpannableStringBuilder() val song = MusicPlayerRemote.currentSong + + val builder = SpannableStringBuilder() + val title = SpannableString(song.title) title.setSpan(ForegroundColorSpan(textColorPrimary()), 0, title.length, 0) @@ -99,17 +102,34 @@ open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_p builder.append(title).append(" • ").append(text) - miniPlayerTitle.isSelected = true - miniPlayerTitle.text = builder + binding.miniPlayerTitle.isSelected = true + binding.miniPlayerTitle.text = builder + +// binding.title.isSelected = true +// binding.title.text = song.title +// binding.text.isSelected = true +// binding.text.text = song.artistName + } + + private fun updateSongCover() { +// val song = MusicPlayerRemote.currentSong +// GlideApp.with(requireContext()) +// .asBitmap() +// .songCoverOptions(song) +// .transition(RetroGlideExtension.getDefaultTransition()) +// .load(RetroGlideExtension.getSongModel(song)) +// .into(binding.image) } override fun onServiceConnected() { updateSongTitle() + updateSongCover() updatePlayPauseDrawableState() } override fun onPlayingMetaChanged() { updateSongTitle() + updateSongCover() } override fun onPlayStateChanged() { @@ -117,8 +137,8 @@ open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_p } override fun onUpdateProgressViews(progress: Int, total: Int) { - progressBar.max = total - val animator = ObjectAnimator.ofInt(progressBar, "progress", progress) + binding.progressBar.max = total + val animator = ObjectAnimator.ofInt(binding.progressBar, "progress", progress) animator.duration = 1000 animator.interpolator = DecelerateInterpolator() animator.start() @@ -136,16 +156,12 @@ open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_p protected fun updatePlayPauseDrawableState() { if (MusicPlayerRemote.isPlaying) { - miniPlayerPlayPauseButton.setImageResource(R.drawable.ic_pause) + binding.miniPlayerPlayPauseButton.setImageResource(R.drawable.ic_pause) } else { - miniPlayerPlayPauseButton.setImageResource(R.drawable.ic_play_arrow) + binding.miniPlayerPlayPauseButton.setImageResource(R.drawable.ic_play_arrow) } } - fun updateProgressBar(paletteColor: Int) { - progressBar.applyColor(paletteColor) - } - class FlingPlayBackController(context: Context) : View.OnTouchListener { private var flingPlayBackController: GestureDetector @@ -178,4 +194,9 @@ open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_p return flingPlayBackController.onTouchEvent(event) } } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/VolumeFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/VolumeFragment.kt index f08c8552..27c8b92a 100755 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/VolumeFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/VolumeFragment.kt @@ -26,34 +26,38 @@ import android.widget.SeekBar import androidx.fragment.app.Fragment import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentVolumeBinding import code.name.monkey.retromusic.extensions.applyColor import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.volume.AudioVolumeObserver import code.name.monkey.retromusic.volume.OnAudioVolumeChangedListener -import kotlinx.android.synthetic.main.fragment_volume.* class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolumeChangedListener, View.OnClickListener { + private var _binding: FragmentVolumeBinding? = null + private val binding get() = _binding!! + private var audioVolumeObserver: AudioVolumeObserver? = null - private val audioManager: AudioManager? + private val audioManager: AudioManager get() = requireContext().getSystemService(Context.AUDIO_SERVICE) as AudioManager override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { - return inflater.inflate(R.layout.fragment_volume, container, false) + ): View { + _binding = FragmentVolumeBinding.inflate(inflater, container, false) + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setTintable(ThemeStore.accentColor(requireContext())) - volumeDown.setOnClickListener(this) - volumeUp.setOnClickListener(this) + binding.volumeDown.setOnClickListener(this) + binding.volumeUp.setOnClickListener(this) } override fun onResume() { @@ -64,33 +68,30 @@ class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolum audioVolumeObserver?.register(AudioManager.STREAM_MUSIC, this) val audioManager = audioManager - if (audioManager != null) { - volumeSeekBar.max = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) - volumeSeekBar.progress = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) - } - volumeSeekBar.setOnSeekBarChangeListener(this) + binding.volumeSeekBar.max = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) + binding.volumeSeekBar.progress = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) + binding.volumeSeekBar.setOnSeekBarChangeListener(this) } override fun onAudioVolumeChanged(currentVolume: Int, maxVolume: Int) { - if (volumeSeekBar == null) { - return + if (_binding != null) { + binding.volumeSeekBar.max = maxVolume + binding.volumeSeekBar.progress = currentVolume + binding.volumeDown.setImageResource(if (currentVolume == 0) R.drawable.ic_volume_off else R.drawable.ic_volume_down) } - - volumeSeekBar.max = maxVolume - volumeSeekBar.progress = currentVolume - volumeDown.setImageResource(if (currentVolume == 0) R.drawable.ic_volume_off else R.drawable.ic_volume_down) } override fun onDestroyView() { super.onDestroyView() audioVolumeObserver?.unregister() + _binding = null } override fun onProgressChanged(seekBar: SeekBar, i: Int, b: Boolean) { val audioManager = audioManager - audioManager?.setStreamVolume(AudioManager.STREAM_MUSIC, i, 0) + audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, i, 0) setPauseWhenZeroVolume(i < 1) - volumeDown?.setImageResource(if (i == 0) R.drawable.ic_volume_off else R.drawable.ic_volume_down) + binding.volumeDown.setImageResource(if (i == 0) R.drawable.ic_volume_off else R.drawable.ic_volume_down) } override fun onStartTrackingTouch(seekBar: SeekBar) { @@ -102,10 +103,10 @@ class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolum override fun onClick(view: View) { val audioManager = audioManager when (view.id) { - R.id.volumeDown -> audioManager?.adjustStreamVolume( + R.id.volumeDown -> audioManager.adjustStreamVolume( AudioManager.STREAM_MUSIC, AudioManager.ADJUST_LOWER, 0 ) - R.id.volumeUp -> audioManager?.adjustStreamVolume( + R.id.volumeUp -> audioManager.adjustStreamVolume( AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, 0 ) } @@ -113,13 +114,13 @@ class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolum fun tintWhiteColor() { val color = Color.WHITE - volumeDown.setColorFilter(color, PorterDuff.Mode.SRC_IN) - volumeUp.setColorFilter(color, PorterDuff.Mode.SRC_IN) - volumeSeekBar.applyColor(color) + binding.volumeDown.setColorFilter(color, PorterDuff.Mode.SRC_IN) + binding.volumeUp.setColorFilter(color, PorterDuff.Mode.SRC_IN) + binding.volumeSeekBar.applyColor(color) } fun setTintable(color: Int) { - volumeSeekBar.applyColor(color) + binding.volumeSeekBar.applyColor(color) } private fun setPauseWhenZeroVolume(pauseWhenZeroVolume: Boolean) { @@ -129,10 +130,10 @@ class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolum } fun setTintableColor(color: Int) { - volumeDown.setColorFilter(color, PorterDuff.Mode.SRC_IN) - volumeUp.setColorFilter(color, PorterDuff.Mode.SRC_IN) + binding.volumeDown.setColorFilter(color, PorterDuff.Mode.SRC_IN) + binding.volumeUp.setColorFilter(color, PorterDuff.Mode.SRC_IN) // TintHelper.setTint(volumeSeekBar, color, false) - volumeSeekBar.applyColor(color) + binding.volumeSeekBar.applyColor(color) } companion object { diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/about/AboutFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/about/AboutFragment.kt index 86f5b2ed..72cac439 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/about/AboutFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/about/AboutFragment.kt @@ -27,20 +27,20 @@ import code.name.monkey.retromusic.App import code.name.monkey.retromusic.Constants import code.name.monkey.retromusic.R import code.name.monkey.retromusic.adapter.ContributorAdapter +import code.name.monkey.retromusic.databinding.FragmentAboutBinding import code.name.monkey.retromusic.fragments.LibraryViewModel import code.name.monkey.retromusic.util.NavigationUtil -import kotlinx.android.synthetic.main.card_credit.* -import kotlinx.android.synthetic.main.card_other.* -import kotlinx.android.synthetic.main.card_retro_info.* -import kotlinx.android.synthetic.main.card_social.* import org.koin.androidx.viewmodel.ext.android.sharedViewModel class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener { + private var _binding: FragmentAboutBinding? = null + private val binding get() = _binding!! private val libraryViewModel by sharedViewModel() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - version.setSummary(getAppVersion()) + _binding = FragmentAboutBinding.bind(view) + binding.aboutContent.cardOther.version.setSummary(getAppVersion()) setUpView() loadContributors() } @@ -53,19 +53,20 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener { } private fun setUpView() { - appGithub.setOnClickListener(this) - faqLink.setOnClickListener(this) - telegramLink.setOnClickListener(this) - appRate.setOnClickListener(this) - appTranslation.setOnClickListener(this) - appShare.setOnClickListener(this) - donateLink.setOnClickListener(this) - instagramLink.setOnClickListener(this) - twitterLink.setOnClickListener(this) - changelog.setOnClickListener(this) - openSource.setOnClickListener(this) - pinterestLink.setOnClickListener(this) - bugReportLink.setOnClickListener(this) + binding.aboutContent.cardRetroInfo.appGithub.setOnClickListener(this) + binding.aboutContent.cardRetroInfo.faqLink.setOnClickListener(this) + binding.aboutContent.cardSocial.telegramLink.setOnClickListener(this) + binding.aboutContent.cardRetroInfo.appRate.setOnClickListener(this) + binding.aboutContent.cardRetroInfo.appTranslation.setOnClickListener(this) + binding.aboutContent.cardRetroInfo.appShare.setOnClickListener(this) + binding.aboutContent.cardRetroInfo.donateLink.setOnClickListener(this) + binding.aboutContent.cardSocial.instagramLink.setOnClickListener(this) + binding.aboutContent.cardSocial.twitterLink.setOnClickListener(this) + binding.aboutContent.cardOther.changelog.setOnClickListener(this) + binding.aboutContent.cardOther.openSource.setOnClickListener(this) + binding.aboutContent.cardSocial.pinterestLink.setOnClickListener(this) + binding.aboutContent.cardRetroInfo.bugReportLink.setOnClickListener(this) + binding.aboutContent.cardSocial.websiteLink.setOnClickListener(this) } override fun onClick(view: View) { @@ -80,9 +81,10 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener { R.id.donateLink -> NavigationUtil.goToSupportDevelopment(requireActivity()) R.id.instagramLink -> openUrl(Constants.APP_INSTAGRAM_LINK) R.id.twitterLink -> openUrl(Constants.APP_TWITTER_LINK) - R.id.changelog -> openUrl(Constants.TELEGRAM_CHANGE_LOG) + R.id.changelog -> NavigationUtil.gotoWhatNews(requireActivity()) R.id.openSource -> NavigationUtil.goToOpenSource(requireActivity()) R.id.bugReportLink -> NavigationUtil.bugReport(requireActivity()) + R.id.websiteLink -> openUrl(Constants.WEBSITE) } } @@ -99,7 +101,7 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener { } private fun shareApp() { - ShareCompat.IntentBuilder.from(requireActivity()).setType("text/plain") + ShareCompat.IntentBuilder(requireActivity()).setType("text/plain") .setChooserTitle(R.string.share_app) .setText(String.format(getString(R.string.app_share), requireActivity().packageName)) .startChooser() @@ -107,7 +109,7 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener { private fun loadContributors() { val contributorAdapter = ContributorAdapter(emptyList()) - recyclerView.apply { + binding.aboutContent.cardCredit.recyclerView.apply { layoutManager = LinearLayoutManager(requireContext()) itemAnimator = DefaultItemAnimator() adapter = contributorAdapter @@ -116,4 +118,9 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener { contributorAdapter.swapData(contributors) }) } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsFragment.kt index 96c47095..c9291185 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsFragment.kt @@ -19,11 +19,12 @@ import android.content.Intent import android.graphics.Color import android.os.Bundle import android.view.* +import androidx.activity.addCallback 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.core.view.doOnPreDraw import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.navigation.fragment.findNavController @@ -35,17 +36,19 @@ import code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackg 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.EXTRA_ARTIST_NAME 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.databinding.FragmentAlbumDetailsBinding 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.GlideApp +import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.glide.SingleColorTarget import code.name.monkey.retromusic.helper.MusicPlayerRemote @@ -54,6 +57,7 @@ import code.name.monkey.retromusic.helper.SortOrder.AlbumSongSortOrder.Companion import code.name.monkey.retromusic.helper.SortOrder.AlbumSongSortOrder.Companion.SONG_TRACK_LIST import code.name.monkey.retromusic.helper.SortOrder.AlbumSongSortOrder.Companion.SONG_Z_A import code.name.monkey.retromusic.interfaces.IAlbumClickListener +import code.name.monkey.retromusic.interfaces.ICabHolder import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.network.Result @@ -61,14 +65,12 @@ 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.RetroColorUtil import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import com.bumptech.glide.Glide +import com.afollestad.materialcab.MaterialCab import com.google.android.material.transition.MaterialArcMotion import com.google.android.material.transition.MaterialContainerTransform -import com.google.android.material.transition.MaterialElevationScale -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 @@ -78,7 +80,10 @@ import org.koin.core.parameter.parametersOf import java.util.* class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_details), - IAlbumClickListener { + IAlbumClickListener, ICabHolder { + + private var _binding: FragmentAlbumDetailsBinding? = null + private val binding get() = _binding!! private val arguments by navArgs() private val detailsViewModel by viewModel { @@ -87,6 +92,7 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det private lateinit var simpleSongAdapter: SimpleSongAdapter private lateinit var album: Album + private var albumArtistExists = false private val savedSortOrder: String get() = PreferenceUtil.albumDetailSongSortOrder @@ -104,51 +110,70 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentAlbumDetailsBinding.bind(view) setHasOptionsMenu(true) - mainActivity.setBottomBarVisibility(false) mainActivity.addMusicServiceEventListener(detailsViewModel) - mainActivity.setSupportActionBar(toolbar) + mainActivity.setSupportActionBar(binding.toolbar) - toolbar.title = " " - ViewCompat.setTransitionName(albumCoverContainer, "album") + binding.toolbar.title = " " + ViewCompat.setTransitionName(binding.albumCoverContainer, arguments.extraAlbumId.toString()) postponeEnterTransition() - detailsViewModel.getAlbum().observe(viewLifecycleOwner, Observer { - startPostponedEnterTransition() + detailsViewModel.getAlbum().observe(viewLifecycleOwner, { + requireView().doOnPreDraw { + startPostponedEnterTransition() + } + albumArtistExists = !it.albumArtist.isNullOrEmpty() showAlbum(it) + if (albumArtistExists) { + ViewCompat.setTransitionName(binding.artistImage, album.albumArtist) + } else { + ViewCompat.setTransitionName(binding.artistImage, album.artistId.toString()) + } }) setupRecyclerView() - artistImage.setOnClickListener { artistView -> - ViewCompat.setTransitionName(artistView, "artist") - exitTransition = MaterialElevationScale(false).apply { - duration = 300L + binding.artistImage.setOnClickListener { artistView -> + if (albumArtistExists) { + findActivityNavController(R.id.fragment_container) + .navigate( + R.id.albumArtistDetailsFragment, + bundleOf(EXTRA_ARTIST_NAME to album.albumArtist), + null, + FragmentNavigatorExtras(artistView to album.albumArtist.toString()) + ) + } else { + findActivityNavController(R.id.fragment_container) + .navigate( + R.id.artistDetailsFragment, + bundleOf(EXTRA_ARTIST_ID to album.artistId), + null, + FragmentNavigatorExtras(artistView to album.artistId.toString()) + ) } - reenterTransition = MaterialElevationScale(true).apply { - duration = 300L - } - findActivityNavController(R.id.fragment_container) - .navigate( - R.id.artistDetailsFragment, - bundleOf(EXTRA_ARTIST_ID to album.artistId), - null, - FragmentNavigatorExtras(artistView to "artist") - ) + } - playAction.setOnClickListener { + binding.fragmentAlbumContent.playAction.setOnClickListener { MusicPlayerRemote.openQueue(album.songs, 0, true) } - shuffleAction.setOnClickListener { + binding.fragmentAlbumContent.shuffleAction.setOnClickListener { MusicPlayerRemote.openAndShuffleQueue( album.songs, true ) } - aboutAlbumText.setOnClickListener { - if (aboutAlbumText.maxLines == 4) { - aboutAlbumText.maxLines = Integer.MAX_VALUE + binding.fragmentAlbumContent.aboutAlbumText.setOnClickListener { + if (binding.fragmentAlbumContent.aboutAlbumText.maxLines == 4) { + binding.fragmentAlbumContent.aboutAlbumText.maxLines = Integer.MAX_VALUE } else { - aboutAlbumText.maxLines = 4 + binding.fragmentAlbumContent.aboutAlbumText.maxLines = 4 + } + } + + requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { + if (!handleBackPress()) { + remove() + requireActivity().onBackPressed() } } } @@ -163,9 +188,9 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det requireActivity() as AppCompatActivity, ArrayList(), R.layout.item_song, - null + this ) - recyclerView.apply { + binding.fragmentAlbumContent.recyclerView.apply { layoutManager = LinearLayoutManager(requireContext()) itemAnimator = DefaultItemAnimator() isNestedScrollingEnabled = false @@ -179,21 +204,21 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det } this.album = album - albumTitle.text = album.title + binding.albumTitle.text = album.title val songText = resources.getQuantityString( R.plurals.albumSongs, album.songCount, album.songCount ) - songTitle.text = songText + binding.fragmentAlbumContent.songTitle.text = songText if (MusicUtil.getYearString(album.year) == "-") { - albumText.text = String.format( + binding.albumText.text = String.format( "%s • %s", - album.artistName, + if (albumArtistExists) album.albumArtist else album.artistName, MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(album.songs)) ) } else { - albumText.text = String.format( + binding.albumText.text = String.format( "%s • %s • %s", album.artistName, MusicUtil.getYearString(album.year), @@ -202,11 +227,18 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det } loadAlbumCover(album) simpleSongAdapter.swapDataSet(album.songs) - detailsViewModel.getArtist(album.artistId).observe(viewLifecycleOwner, Observer { - loadArtistImage(it) - }) + if (albumArtistExists) { + detailsViewModel.getAlbumArtist(album.albumArtist.toString()).observe(viewLifecycleOwner, { + loadArtistImage(it) + }) + } else { + detailsViewModel.getArtist(album.artistId).observe(viewLifecycleOwner, { + loadArtistImage(it) + }) + } - detailsViewModel.getAlbumInfo(album).observe(viewLifecycleOwner, Observer { result -> + + detailsViewModel.getAlbumInfo(album).observe(viewLifecycleOwner, { result -> when (result) { is Result.Loading -> { println("Loading") @@ -222,41 +254,44 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det } private fun moreAlbums(albums: List) { - moreTitle.show() - moreRecyclerView.show() - moreTitle.text = String.format(getString(R.string.label_more_from), album.artistName) + binding.fragmentAlbumContent.moreTitle.show() + binding.fragmentAlbumContent.moreRecyclerView.show() + binding.fragmentAlbumContent.moreTitle.text = + String.format(getString(R.string.label_more_from), album.artistName) val albumAdapter = - HorizontalAlbumAdapter(requireActivity() as AppCompatActivity, albums, null, this) - moreRecyclerView.layoutManager = GridLayoutManager( + HorizontalAlbumAdapter(requireActivity() as AppCompatActivity, albums, this, this) + binding.fragmentAlbumContent.moreRecyclerView.layoutManager = GridLayoutManager( requireContext(), 1, GridLayoutManager.HORIZONTAL, false ) - moreRecyclerView.adapter = albumAdapter + binding.fragmentAlbumContent.moreRecyclerView.adapter = albumAdapter } private fun aboutAlbum(lastFmAlbum: LastFmAlbum) { if (lastFmAlbum.album != null) { if (lastFmAlbum.album.wiki != null) { - aboutAlbumText.show() - aboutAlbumTitle.show() - aboutAlbumTitle.text = + binding.fragmentAlbumContent.aboutAlbumText.show() + binding.fragmentAlbumContent.aboutAlbumTitle.show() + binding.fragmentAlbumContent.aboutAlbumTitle.text = String.format(getString(R.string.about_album_label), lastFmAlbum.album.name) - aboutAlbumText.text = HtmlCompat.fromHtml( + binding.fragmentAlbumContent.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() + binding.fragmentAlbumContent.listeners.show() + binding.fragmentAlbumContent.listenersLabel.show() + binding.fragmentAlbumContent.scrobbles.show() + binding.fragmentAlbumContent.scrobblesLabel.show() - listeners.text = RetroUtil.formatValue(lastFmAlbum.album.listeners.toFloat()) - scrobbles.text = RetroUtil.formatValue(lastFmAlbum.album.playcount.toFloat()) + binding.fragmentAlbumContent.listeners.text = + RetroUtil.formatValue(lastFmAlbum.album.listeners.toFloat()) + binding.fragmentAlbumContent.scrobbles.text = + RetroUtil.formatValue(lastFmAlbum.album.playcount.toFloat()) } } } @@ -265,24 +300,22 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det detailsViewModel.getMoreAlbums(artist).observe(viewLifecycleOwner, { moreAlbums(it) }) - ArtistGlideRequest.Builder.from(Glide.with(requireContext()), artist) - .forceDownload(PreferenceUtil.isAllowedToDownloadMetadata()) - .generatePalette(requireContext()) - .build() + GlideApp.with(requireContext()).asBitmapPalette().artistImageOptions(artist) + //.forceDownload(PreferenceUtil.isAllowedToDownloadMetadata()) + .load(RetroGlideExtension.getArtistModel(artist, PreferenceUtil.isAllowedToDownloadMetadata())) .dontAnimate() .dontTransform() - .into(object : RetroMusicColoredTarget(artistImage) { + .into(object : RetroMusicColoredTarget(binding.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) { + GlideApp.with(requireContext()).asBitmapPalette().albumCoverOptions(album.safeGetFirstSong()) + //.checkIgnoreMediaStore() + .load(RetroGlideExtension.getSongModel(album.safeGetFirstSong())) + .into(object : SingleColorTarget(binding.image) { override fun onColorReady(color: Int) { setColors(color) } @@ -290,23 +323,17 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det } private fun setColors(color: Int) { - shuffleAction?.applyColor(color) - playAction?.applyOutlineColor(color) + binding.fragmentAlbumContent.shuffleAction.applyColor(color) + binding.fragmentAlbumContent.playAction.applyOutlineColor(color) } override fun onAlbumClick(albumId: Long, view: View) { - exitTransition = MaterialElevationScale(false).apply { - duration = 300L - } - reenterTransition = MaterialElevationScale(false).apply { - duration = 300L - } findNavController().navigate( R.id.albumDetailsFragment, bundleOf(EXTRA_ALBUM_ID to albumId), null, FragmentNavigatorExtras( - view to "album" + view to albumId.toString() ) ) } @@ -318,9 +345,9 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det setUpSortOrderMenu(sortOrder.subMenu) ToolbarContentTintHelper.handleOnCreateOptionsMenu( requireContext(), - toolbar, + binding.toolbar, menu, - getToolbarBackgroundColor(toolbar) + getToolbarBackgroundColor(binding.toolbar) ) } @@ -360,7 +387,7 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det intent.putExtra(AbsTagEditorActivity.EXTRA_ID, album.id) val options = ActivityOptions.makeSceneTransitionAnimation( requireActivity(), - albumCoverContainer, + binding.albumCoverContainer, "${getString(R.string.transition_album_art)}_${album.id}" ) startActivityForResult( @@ -421,6 +448,37 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det simpleSongAdapter.swapDataSet(album.songs) } + private fun handleBackPress(): Boolean { + cab?.let { + if (it.isActive) { + it.finish() + return true + } + } + return false + } + + private var cab: MaterialCab? = null + + override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab { + cab?.let { + if (it.isActive) { + it.finish() + } + } + cab = MaterialCab(mainActivity, R.id.cab_stub) + .setMenu(menuRes) + .setCloseDrawableRes(R.drawable.ic_close) + .setBackgroundColor(RetroColorUtil.shiftBackgroundColorForLightText(surfaceColor())) + .start(callback) + return cab as MaterialCab + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + companion object { const val TAG_EDITOR_REQUEST = 9002 } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsViewModel.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsViewModel.kt index 0dfa0ba7..702217d2 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsViewModel.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsViewModel.kt @@ -14,11 +14,7 @@ */ package code.name.monkey.retromusic.fragments.albums -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.liveData -import androidx.lifecycle.viewModelScope +import androidx.lifecycle.* import code.name.monkey.retromusic.interfaces.IMusicServiceEventListener import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.model.Artist @@ -51,6 +47,11 @@ class AlbumDetailsViewModel( emit(artist) } + fun getAlbumArtist(artistName: String): LiveData = liveData(IO) { + val artist = repository.albumArtistByName(artistName) + emit(artist) + } + fun getAlbumInfo(album: Album): LiveData> = liveData { emit(Result.Loading) emit(repository.albumInfo(album.artistName ?: "-", album.title ?: "-")) @@ -73,4 +74,5 @@ class AlbumDetailsViewModel( override fun onPlayStateChanged() {} override fun onRepeatModeChanged() {} override fun onShuffleModeChanged() {} + override fun onFavoriteStateChanged() {} } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumsFragment.kt index 4df87cf9..92a372fe 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumsFragment.kt @@ -16,6 +16,7 @@ package code.name.monkey.retromusic.fragments.albums import android.os.Bundle import android.view.* +import androidx.activity.addCallback import androidx.core.os.bundleOf import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.navigation.fragment.findNavController @@ -33,7 +34,7 @@ import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.RetroColorUtil import code.name.monkey.retromusic.util.RetroUtil import com.afollestad.materialcab.MaterialCab -import com.google.android.material.transition.MaterialElevationScale +import com.google.android.gms.cast.framework.CastButtonFactory class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment(), IAlbumClickListener, ICabHolder { @@ -46,8 +47,17 @@ class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment AlbumSortOrder.ALBUM_Z_A R.id.action_album_sort_order_artist -> AlbumSortOrder.ALBUM_ARTIST R.id.action_album_sort_order_year -> AlbumSortOrder.ALBUM_YEAR + R.id.action_album_sort_order_num_songs -> AlbumSortOrder.ALBUM_NUMBER_OF_SONGS else -> PreferenceUtil.albumSortOrder } if (sortOrder != PreferenceUtil.albumSortOrder) { @@ -303,6 +318,21 @@ class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment + when (result) { + is Result.Loading -> println("Loading") + is Result.Error -> println("Error") + is Result.Success -> artistInfo(result.data) + } + }) + } + + private fun artistInfo(lastFmArtist: LastFmArtist?) { + if (lastFmArtist != null && lastFmArtist.artist != null && lastFmArtist.artist.bio != null) { + val bioContent = lastFmArtist.artist.bio.content + if (bioContent != null && bioContent.trim { it <= ' ' }.isNotEmpty()) { + binding.fragmentArtistContent.biographyText.visibility = View.VISIBLE + binding.fragmentArtistContent.biographyTitle.visibility = View.VISIBLE + biography = HtmlCompat.fromHtml(bioContent, HtmlCompat.FROM_HTML_MODE_LEGACY) + binding.fragmentArtistContent.biographyText.text = biography + if (lastFmArtist.artist.stats.listeners.isNotEmpty()) { + binding.fragmentArtistContent.listeners.show() + binding.fragmentArtistContent.listenersLabel.show() + binding.fragmentArtistContent.scrobbles.show() + binding.fragmentArtistContent.scrobblesLabel.show() + binding.fragmentArtistContent.listeners.text = + RetroUtil.formatValue(lastFmArtist.artist.stats.listeners.toFloat()) + binding.fragmentArtistContent.scrobbles.text = + RetroUtil.formatValue(lastFmArtist.artist.stats.playcount.toFloat()) + } + } + } + + // If the "lang" parameter is set and no biography is given, retry with default language + if (biography == null && lang != null) { + loadBiography(artist.name, null) + } + } + + private fun loadArtistImage(artist: Artist) { + GlideApp.with(requireContext()).asBitmapPalette().artistImageOptions(artist) + .load(RetroGlideExtension.getArtistModel(artist)) + .dontAnimate() + .into(object : SingleColorTarget(binding.image) { + override fun onColorReady(color: Int) { + setColors(color) + } + }) + } + + private fun setColors(color: Int) { + if (_binding != null) { + binding.fragmentArtistContent.shuffleAction.applyColor(color) + binding.fragmentArtistContent.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 albumId.toString() + ) + ) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return handleSortOrderMenuItem(item) + } + + private fun handleSortOrderMenuItem(item: MenuItem): Boolean { + val songs = artist.songs + 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_set_artist_image -> { + val intent = Intent(Intent.ACTION_GET_CONTENT) + intent.type = "image/*" + startActivityForResult( + Intent.createChooser(intent, getString(R.string.pick_from_local_storage)), + REQUEST_CODE_SELECT_IMAGE + ) + return true + } + R.id.action_reset_artist_image -> { + showToast(resources.getString(R.string.updating)) + CustomArtistImageUtil.getInstance(requireContext()).resetCustomArtistImage(artist) + forceDownload = true + return true + } + } + return true + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + when (requestCode) { + REQUEST_CODE_SELECT_IMAGE -> if (resultCode == Activity.RESULT_OK) { + data?.data?.let { + CustomArtistImageUtil.getInstance(requireContext()) + .setCustomArtistImage(artist, it) + } + } + else -> if (resultCode == Activity.RESULT_OK) { + println("OK") + } + } + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + super.onCreateOptionsMenu(menu, inflater) + inflater.inflate(R.menu.menu_artist_detail, menu) + } + + + private fun handleBackPress(): Boolean { + cab?.let { + if (it.isActive) { + it.finish() + return true + } + } + return false + } + + private var cab: MaterialCab? = null + + override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab { + cab?.let { + if (it.isActive) { + it.finish() + } + } + cab = MaterialCab(mainActivity, R.id.cab_stub) + .setMenu(menuRes) + .setCloseDrawableRes(R.drawable.ic_close) + .setBackgroundColor(RetroColorUtil.shiftBackgroundColorForLightText(surfaceColor())) + .start(callback) + return cab as MaterialCab + } + + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + companion object { + const val REQUEST_CODE_SELECT_IMAGE = 9002 + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/artists/AlbumArtistDetailsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/artists/AlbumArtistDetailsFragment.kt new file mode 100644 index 00000000..3480ac15 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/artists/AlbumArtistDetailsFragment.kt @@ -0,0 +1,32 @@ +/* + * 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.artists + +import androidx.navigation.fragment.navArgs +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf + +class AlbumArtistDetailsFragment : AbsArtistDetailsFragment() { + + private val arguments by navArgs() + + override val detailsViewModel: ArtistDetailsViewModel by viewModel { + parametersOf(null, arguments.extraArtistName) + } + override val artistId: Long? + get() = null + override val artistName: String + get() = arguments.extraArtistName +} diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/artists/AlbumArtistDetailsViewModel.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/artists/AlbumArtistDetailsViewModel.kt new file mode 100644 index 00000000..3d202bb5 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/artists/AlbumArtistDetailsViewModel.kt @@ -0,0 +1,66 @@ +/* + * 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.artists + +import androidx.lifecycle.* +import code.name.monkey.retromusic.interfaces.IMusicServiceEventListener +import code.name.monkey.retromusic.model.Artist +import code.name.monkey.retromusic.network.Result +import code.name.monkey.retromusic.network.model.LastFmArtist +import code.name.monkey.retromusic.repository.RealRepository +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.launch + +class AlbumArtistDetailsViewModel( + private val realRepository: RealRepository, + private val artistName: String +) : ViewModel(), IMusicServiceEventListener { + private val artistDetails = MutableLiveData() + + init { + fetchAlbumArtist() + } + + private fun fetchAlbumArtist() { + viewModelScope.launch(IO) { + artistDetails.postValue(realRepository.albumArtistByName(artistName)) + } + } + + fun getArtist(): LiveData = artistDetails + + fun getArtistInfo( + name: String, + lang: String?, + cache: String? + ): LiveData> = liveData(IO) { + emit(Result.Loading) + val info = realRepository.artistInfo(name, lang, cache) + emit(info) + } + + override fun onMediaStoreChanged() { + fetchAlbumArtist() + } + + override fun onServiceConnected() {} + override fun onServiceDisconnected() {} + override fun onQueueChanged() {} + override fun onFavoriteStateChanged() {} + override fun onPlayingMetaChanged() {} + override fun onPlayStateChanged() {} + override fun onRepeatModeChanged() {} + override fun onShuffleModeChanged() {} +} diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistDetailsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistDetailsFragment.kt index 2aecf153..6e556a24 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistDetailsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistDetailsFragment.kt @@ -14,298 +14,18 @@ */ package code.name.monkey.retromusic.fragments.artists -import android.app.Activity -import android.content.Intent -import android.graphics.Color -import android.os.Bundle -import android.text.Spanned -import android.view.Menu -import android.view.MenuInflater -import android.view.MenuItem -import android.view.View -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.util.ATHUtil -import code.name.monkey.retromusic.EXTRA_ALBUM_ID -import code.name.monkey.retromusic.R -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.extensions.* -import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment -import code.name.monkey.retromusic.glide.ArtistGlideRequest -import code.name.monkey.retromusic.glide.SingleColorTarget -import code.name.monkey.retromusic.helper.MusicPlayerRemote -import code.name.monkey.retromusic.interfaces.IAlbumClickListener -import code.name.monkey.retromusic.model.Artist -import code.name.monkey.retromusic.network.Result -import code.name.monkey.retromusic.network.model.LastFmArtist -import code.name.monkey.retromusic.repository.RealRepository -import code.name.monkey.retromusic.util.CustomArtistImageUtil -import code.name.monkey.retromusic.util.MusicUtil -import code.name.monkey.retromusic.util.RetroUtil -import com.bumptech.glide.Glide -import com.google.android.material.transition.MaterialContainerTransform -import com.google.android.material.transition.MaterialElevationScale -import kotlinx.android.synthetic.main.fragment_artist_content.* -import kotlinx.android.synthetic.main.fragment_artist_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.* -import kotlin.collections.ArrayList -class ArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_artist_details), - IAlbumClickListener { +class ArtistDetailsFragment : AbsArtistDetailsFragment() { private val arguments by navArgs() - private val detailsViewModel: ArtistDetailsViewModel by viewModel { - parametersOf(arguments.extraArtistId) + override val detailsViewModel: ArtistDetailsViewModel by viewModel { + parametersOf(arguments.extraArtistId, null) } - private lateinit var artist: Artist - private lateinit var songAdapter: SimpleSongAdapter - private lateinit var albumAdapter: HorizontalAlbumAdapter - private var forceDownload: Boolean = false - private var lang: String? = null - private var biography: Spanned? = null + override val artistId: Long + get() = arguments.extraArtistId + override val artistName: String? + get() = null - 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)) - } - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - setHasOptionsMenu(true) - mainActivity.setBottomBarVisibility(false) - mainActivity.addMusicServiceEventListener(detailsViewModel) - mainActivity.setSupportActionBar(toolbar) - toolbar.title = null - ViewCompat.setTransitionName(artistCoverContainer, "artist") - postponeEnterTransition() - detailsViewModel.getArtist().observe(viewLifecycleOwner, { - startPostponedEnterTransition() - showArtist(it) - }) - setupRecyclerView() - - playAction.apply { - setOnClickListener { MusicPlayerRemote.openQueue(artist.songs, 0, true) } - } - shuffleAction.apply { - setOnClickListener { MusicPlayerRemote.openAndShuffleQueue(artist.songs, true) } - } - - biographyText.setOnClickListener { - if (biographyText.maxLines == 4) { - biographyText.maxLines = Integer.MAX_VALUE - } else { - biographyText.maxLines = 4 - } - } - } - - private fun setupRecyclerView() { - albumAdapter = HorizontalAlbumAdapter(requireActivity(), ArrayList(), null, this) - albumRecyclerView.apply { - itemAnimator = DefaultItemAnimator() - layoutManager = GridLayoutManager(this.context, 1, GridLayoutManager.HORIZONTAL, false) - adapter = albumAdapter - } - songAdapter = SimpleSongAdapter(requireActivity(), ArrayList(), R.layout.item_song, null) - recyclerView.apply { - itemAnimator = DefaultItemAnimator() - layoutManager = LinearLayoutManager(this.context) - adapter = songAdapter - } - } - - private fun showArtist(artist: Artist) { - this.artist = artist - loadArtistImage(artist) - if (RetroUtil.isAllowedToDownloadMetadata(requireContext())) { - loadBiography(artist.name) - } - artistTitle.text = artist.name - text.text = String.format( - "%s • %s", - MusicUtil.getArtistInfoString(requireContext(), artist), - MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(artist.songs)) - ) - val songText = resources.getQuantityString( - R.plurals.albumSongs, - artist.songCount, - artist.songCount - ) - val albumText = resources.getQuantityString( - R.plurals.albums, - artist.songCount, - artist.songCount - ) - songTitle.text = songText - albumTitle.text = albumText - songAdapter.swapDataSet(artist.songs.sortedBy { it.trackNumber }) - albumAdapter.swapDataSet(artist.albums) - } - - private fun loadBiography( - name: String, - lang: String? = Locale.getDefault().language - ) { - biography = null - this.lang = lang - detailsViewModel.getArtistInfo(name, lang, null) - .observe(viewLifecycleOwner, { result -> - when (result) { - is Result.Loading -> println("Loading") - is Result.Error -> println("Error") - is Result.Success -> artistInfo(result.data) - } - }) - } - - private fun artistInfo(lastFmArtist: LastFmArtist?) { - if (lastFmArtist != null && lastFmArtist.artist != null) { - val bioContent = lastFmArtist.artist.bio.content - if (bioContent != null && bioContent.trim { it <= ' ' }.isNotEmpty()) { - biographyText.visibility = View.VISIBLE - biographyTitle.visibility = View.VISIBLE - biography = HtmlCompat.fromHtml(bioContent, HtmlCompat.FROM_HTML_MODE_LEGACY) - biographyText.text = biography - if (lastFmArtist.artist.stats.listeners.isNotEmpty()) { - listeners.show() - listenersLabel.show() - scrobbles.show() - scrobblesLabel.show() - listeners.text = - RetroUtil.formatValue(lastFmArtist.artist.stats.listeners.toFloat()) - scrobbles.text = - RetroUtil.formatValue(lastFmArtist.artist.stats.playcount.toFloat()) - } - } - } - - // If the "lang" parameter is set and no biography is given, retry with default language - if (biography == null && lang != null) { - loadBiography(artist.name, null) - } - } - - private fun loadArtistImage(artist: Artist) { - ArtistGlideRequest.Builder.from(Glide.with(requireContext()), artist) - .generatePalette(requireContext()).build() - .dontAnimate() - .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) { - exitTransition = MaterialElevationScale(false).apply { - duration = 300L - } - reenterTransition = MaterialElevationScale(false).apply { - duration = 300L - } - findNavController().navigate( - R.id.albumDetailsFragment, - bundleOf(EXTRA_ALBUM_ID to albumId), - null, - FragmentNavigatorExtras( - view to "album" - ) - ) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return handleSortOrderMenuItem(item) - } - - private fun handleSortOrderMenuItem(item: MenuItem): Boolean { - val songs = artist.songs - 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_set_artist_image -> { - val intent = Intent(Intent.ACTION_GET_CONTENT) - intent.type = "image/*" - startActivityForResult( - Intent.createChooser(intent, getString(R.string.pick_from_local_storage)), - REQUEST_CODE_SELECT_IMAGE - ) - return true - } - R.id.action_reset_artist_image -> { - showToast(resources.getString(R.string.updating)) - CustomArtistImageUtil.getInstance(requireContext()).resetCustomArtistImage(artist) - forceDownload = true - return true - } - } - return true - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - when (requestCode) { - REQUEST_CODE_SELECT_IMAGE -> if (resultCode == Activity.RESULT_OK) { - data?.data?.let { - CustomArtistImageUtil.getInstance(requireContext()) - .setCustomArtistImage(artist, it) - } - } - else -> if (resultCode == Activity.RESULT_OK) { - println("OK") - } - } - } - - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - super.onCreateOptionsMenu(menu, inflater) - inflater.inflate(R.menu.menu_artist_detail, menu) - } - - companion object { - const val REQUEST_CODE_SELECT_IMAGE = 9002 - } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistDetailsViewModel.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistDetailsViewModel.kt index dbb59866..ae2eef91 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistDetailsViewModel.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistDetailsViewModel.kt @@ -14,11 +14,7 @@ */ package code.name.monkey.retromusic.fragments.artists -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.liveData -import androidx.lifecycle.viewModelScope +import androidx.lifecycle.* import code.name.monkey.retromusic.interfaces.IMusicServiceEventListener import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.network.Result @@ -29,7 +25,8 @@ import kotlinx.coroutines.launch class ArtistDetailsViewModel( private val realRepository: RealRepository, - private val artistId: Long + private val artistId: Long?, + private val artistName: String? ) : ViewModel(), IMusicServiceEventListener { private val artistDetails = MutableLiveData() @@ -39,7 +36,9 @@ class ArtistDetailsViewModel( private fun fetchArtist() { viewModelScope.launch(IO) { - artistDetails.postValue(realRepository.artistById(artistId)) + artistId?.let { artistDetails.postValue(realRepository.artistById(it)) } + + artistName?.let { artistDetails.postValue(realRepository.albumArtistByName(it)) } } } @@ -56,7 +55,7 @@ class ArtistDetailsViewModel( } override fun onMediaStoreChanged() { - fetchArtist() + fetchArtist() } override fun onServiceConnected() {} @@ -66,4 +65,5 @@ class ArtistDetailsViewModel( override fun onPlayStateChanged() {} override fun onRepeatModeChanged() {} override fun onShuffleModeChanged() {} + override fun onFavoriteStateChanged() {} } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistsFragment.kt index 3f05d6d2..8275c526 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistsFragment.kt @@ -16,38 +16,48 @@ package code.name.monkey.retromusic.fragments.artists import android.os.Bundle import android.view.* +import androidx.activity.addCallback import androidx.core.os.bundleOf -import androidx.lifecycle.Observer import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.GridLayoutManager import code.name.monkey.retromusic.EXTRA_ARTIST_ID +import code.name.monkey.retromusic.EXTRA_ARTIST_NAME import code.name.monkey.retromusic.R import code.name.monkey.retromusic.adapter.artist.ArtistAdapter import code.name.monkey.retromusic.extensions.surfaceColor import code.name.monkey.retromusic.fragments.ReloadType import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewCustomGridSizeFragment import code.name.monkey.retromusic.helper.SortOrder.ArtistSortOrder +import code.name.monkey.retromusic.interfaces.IAlbumArtistClickListener import code.name.monkey.retromusic.interfaces.IArtistClickListener import code.name.monkey.retromusic.interfaces.ICabHolder import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.RetroColorUtil import code.name.monkey.retromusic.util.RetroUtil import com.afollestad.materialcab.MaterialCab -import com.google.android.material.transition.MaterialElevationScale +import com.google.android.gms.cast.framework.CastButtonFactory class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment(), - IArtistClickListener, ICabHolder { + IArtistClickListener, IAlbumArtistClickListener, ICabHolder { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - libraryViewModel.getArtists().observe(viewLifecycleOwner, Observer { + libraryViewModel.getArtists().observe(viewLifecycleOwner, { if (it.isNotEmpty()) adapter?.swapDataSet(it) else adapter?.swapDataSet(listOf()) }) + requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { + if (!handleBackPress()) { + remove() + requireActivity().onBackPressed() + } + } } + override val titleRes: Int + get() = R.string.artists override val emptyMessage: Int get() = R.string.no_artists @@ -66,7 +76,7 @@ class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment { + PreferenceUtil.showLyrics = !PreferenceUtil.showLyrics + showLyricsIcon(item) + return true + } R.id.action_toggle_favorite -> { toggleFavorite(song) return true @@ -114,6 +130,8 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme return true } R.id.action_go_to_album -> { + //Hide Bottom Bar First, else Bottom Sheet doesn't collapse fully + mainActivity.setBottomBarVisibility(false) mainActivity.collapsePanel() requireActivity().findNavController(R.id.fragment_container).navigate( R.id.albumDetailsFragment, @@ -122,11 +140,7 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme return true } R.id.action_go_to_artist -> { - mainActivity.collapsePanel() - requireActivity().findNavController(R.id.fragment_container).navigate( - R.id.artistDetailsFragment, - bundleOf(EXTRA_ARTIST_ID to song.artistId) - ) + goToArtist(requireActivity()) return true } R.id.now_playing -> { @@ -158,7 +172,7 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme val trackUri = ContentUris.withAppendedId( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, - song.id.toLong() + song.id ) retriever.setDataSource(activity, trackUri) var genre: String? = @@ -173,6 +187,18 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme return false } + private fun showLyricsIcon(item: MenuItem) { + val icon = + if (PreferenceUtil.showLyrics) R.drawable.ic_lyrics else R.drawable.ic_lyrics_outline + val drawable: Drawable? = RetroUtil.getTintedVectorDrawable( + requireContext(), + icon, + toolbarIconColor() + ) + item.isChecked = PreferenceUtil.showLyrics + item.icon = drawable + } + abstract fun playerToolbar(): Toolbar? abstract fun onShow() @@ -185,7 +211,6 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme override fun onServiceConnected() { updateIsFavorite() - updateLyrics() } override fun onPlayingMetaChanged() { @@ -193,9 +218,13 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme updateLyrics() } + override fun onFavoriteStateChanged() { + updateIsFavorite(animate = true) + } + protected open fun toggleFavorite(song: Song) { lifecycleScope.launch(IO) { - val playlist: PlaylistEntity? = libraryViewModel.favoritePlaylist() + val playlist: PlaylistEntity = libraryViewModel.favoritePlaylist() if (playlist != null) { val songEntity = song.toSongEntity(playlist.playListId) val isFavorite = libraryViewModel.isFavoriteSong(songEntity).isNotEmpty() @@ -210,26 +239,36 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme } } - fun updateIsFavorite() { + fun updateIsFavorite(animate: Boolean = false) { lifecycleScope.launch(IO) { - val playlist: PlaylistEntity? = libraryViewModel.favoritePlaylist() + val playlist: PlaylistEntity = libraryViewModel.favoritePlaylist() if (playlist != null) { val song: SongEntity = MusicPlayerRemote.currentSong.toSongEntity(playlist.playListId) val isFavorite: Boolean = libraryViewModel.isFavoriteSong(song).isNotEmpty() withContext(Main) { - val icon = + val icon = if (animate) { + if (isFavorite) R.drawable.avd_favorite else R.drawable.avd_unfavorite + } else { if (isFavorite) R.drawable.ic_favorite else R.drawable.ic_favorite_border + } val drawable: Drawable? = RetroUtil.getTintedVectorDrawable( requireContext(), icon, toolbarIconColor() ) if (playerToolbar() != null) { - playerToolbar()?.menu?.findItem(R.id.action_toggle_favorite) - ?.setIcon(drawable)?.title = - if (isFavorite) getString(R.string.action_remove_from_favorites) - else getString(R.string.action_add_to_favorites) + playerToolbar()?.menu?.findItem(R.id.action_toggle_favorite)?.apply { + setIcon(drawable) + title = + if (isFavorite) getString(R.string.action_remove_from_favorites) + else getString(R.string.action_add_to_favorites) + getIcon().also { + if (it is AnimatedVectorDrawable) { + it.start() + } + } + } } } } @@ -273,7 +312,50 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme playerAlbumCoverFragment?.setCallbacks(this) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) - statusBarShadow?.hide() + view.findViewById(R.id.statusBarShadow)?.hide() + } + + @SuppressLint("ClickableViewAccessibility") + override fun onStart() { + super.onStart() + requireView().setOnTouchListener( + SwipeDetector( + requireContext(), + playerAlbumCoverFragment?.viewPager, + requireView() + ) + ) + } + + class SwipeDetector(val context: Context, val viewPager: ViewPager?, val view: View) : + View.OnTouchListener { + private var flingPlayBackController: GestureDetector = GestureDetector( + context, + object : GestureDetector.SimpleOnGestureListener() { + override fun onScroll( + e1: MotionEvent?, + e2: MotionEvent?, + distanceX: Float, + distanceY: Float + ): Boolean { + return when { + abs(distanceX) > abs(distanceY) -> { + // Disallow Intercept Touch Event so that parent(BottomSheet) doesn't consume the events + view.parent.requestDisallowInterceptTouchEvent(true) + true + } + else -> { + false + } + } + } + }) + + @SuppressLint("ClickableViewAccessibility") + override fun onTouch(v: View, event: MotionEvent?): Boolean { + viewPager?.dispatchTouchEvent(event) + return flingPlayBackController.onTouchEvent(event) + } } companion object { @@ -290,3 +372,44 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme ) } } + +fun goToArtist(activity: Activity) { + if (activity !is MainActivity) return + val song = MusicPlayerRemote.currentSong + activity.apply { + + // Remove exit transition of current fragment so + // it doesn't exit with a weird transition + currentFragment(R.id.fragment_container)?.exitTransition = null + + //Hide Bottom Bar First, else Bottom Sheet doesn't collapse fully + setBottomBarVisibility(false) + if (getBottomSheetBehavior().state == BottomSheetBehavior.STATE_EXPANDED) { + collapsePanel() + } + + findNavController(R.id.fragment_container).navigate( + R.id.artistDetailsFragment, + bundleOf(EXTRA_ARTIST_ID to song.artistId) + ) + } +} + +fun goToAlbum(activity: Activity) { + if (activity !is MainActivity) return + val song = MusicPlayerRemote.currentSong + activity.apply { + currentFragment(R.id.fragment_container)?.exitTransition = null + + //Hide Bottom Bar First, else Bottom Sheet doesn't collapse fully + setBottomBarVisibility(false) + if (getBottomSheetBehavior().state == BottomSheetBehavior.STATE_EXPANDED) { + collapsePanel() + } + + findNavController(R.id.fragment_container).navigate( + R.id.albumDetailsFragment, + bundleOf(EXTRA_ALBUM_ID to song.albumId) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/base/AbsRecyclerViewCustomGridSizeFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/base/AbsRecyclerViewCustomGridSizeFragment.kt index 749c8205..5a4723d5 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/base/AbsRecyclerViewCustomGridSizeFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/base/AbsRecyclerViewCustomGridSizeFragment.kt @@ -14,12 +14,12 @@ */ package code.name.monkey.retromusic.fragments.base -import android.os.Bundle -import android.view.View -import androidx.annotation.LayoutRes +import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView +import androidx.transition.TransitionManager import code.name.monkey.retromusic.R import code.name.monkey.retromusic.util.RetroUtil +import com.google.android.material.transition.MaterialFade abstract class AbsRecyclerViewCustomGridSizeFragment, LM : RecyclerView.LayoutManager> : AbsRecyclerViewFragment() { @@ -86,6 +86,7 @@ abstract class AbsRecyclerViewCustomGridSizeFragment } else { saveGridSize(gridSize) } + recyclerView().isVisible = false invalidateLayoutManager() // only recreate the adapter and layout manager if the layout currentLayoutRes has changed if (oldLayoutRes != itemLayoutRes()) { @@ -93,26 +94,11 @@ abstract class AbsRecyclerViewCustomGridSizeFragment } else { setGridSize(gridSize) } - } - - protected fun notifyLayoutResChanged(@LayoutRes res: Int) { - this.currentLayoutRes = res - val recyclerView = recyclerView() - applyRecyclerViewPaddingForLayoutRes(recyclerView, currentLayoutRes) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - applyRecyclerViewPaddingForLayoutRes(recyclerView(), currentLayoutRes) - } - - private fun applyRecyclerViewPaddingForLayoutRes(recyclerView: RecyclerView, res: Int) { - val padding: Int = if (res == R.layout.item_grid) { - (resources.displayMetrics.density * 2).toInt() - } else { - 0 + val transition = MaterialFade().apply { + addTarget(recyclerView()) } - //recyclerView.setPadding(padding, padding, padding, padding) + TransitionManager.beginDelayedTransition(getContainer(), transition) + recyclerView().isVisible = true } protected abstract fun setGridSize(gridSize: Int) diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/base/AbsRecyclerViewFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/base/AbsRecyclerViewFragment.kt index b895d831..87227dc9 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/base/AbsRecyclerViewFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/base/AbsRecyclerViewFragment.kt @@ -15,80 +15,73 @@ package code.name.monkey.retromusic.fragments.base import android.os.Bundle -import android.view.Menu -import android.view.MenuInflater -import android.view.MenuItem -import android.view.View +import android.view.* import androidx.annotation.NonNull import androidx.annotation.StringRes -import androidx.core.text.HtmlCompat +import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.view.doOnPreDraw import androidx.core.view.updatePadding import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.RecyclerView -import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.common.ATHToolbarActivity import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentMainRecyclerBinding import code.name.monkey.retromusic.dialogs.CreatePlaylistDialog import code.name.monkey.retromusic.dialogs.ImportPlaylistDialog import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.util.DensityUtil import code.name.monkey.retromusic.util.ThemedFastScroller.create -import com.google.android.material.appbar.AppBarLayout -import kotlinx.android.synthetic.main.fragment_main_recycler.* +import com.google.android.material.transition.MaterialFadeThrough +import com.google.android.material.transition.MaterialSharedAxis import me.zhanghai.android.fastscroll.FastScroller import me.zhanghai.android.fastscroll.FastScrollerBuilder abstract class AbsRecyclerViewFragment, LM : RecyclerView.LayoutManager> : - AbsMainActivityFragment(R.layout.fragment_main_recycler), - AppBarLayout.OnOffsetChangedListener { - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - setHasOptionsMenu(true) - } + AbsMainActivityFragment(R.layout.fragment_main_recycler) { + private var _binding: FragmentMainRecyclerBinding? = null + private val binding get() = _binding!! protected var adapter: A? = null protected var layoutManager: LM? = null override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - + _binding = FragmentMainRecyclerBinding.bind(view) + enterTransition = MaterialFadeThrough() + exitTransition = MaterialFadeThrough() postponeEnterTransition() view.doOnPreDraw { startPostponedEnterTransition() } - mainActivity.setBottomBarVisibility(true) - mainActivity.setSupportActionBar(toolbar) + mainActivity.setSupportActionBar(binding.toolbar) mainActivity.supportActionBar?.title = null initLayoutManager() initAdapter() setUpRecyclerView() - setupTitle() + setupToolbar() } - private fun setupTitle() { - toolbar.setNavigationOnClickListener { + private fun setupToolbar() { + binding.toolbar.setNavigationOnClickListener { + exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true).addTarget(requireView()) + reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) findNavController().navigate( R.id.searchFragment, null, navOptions ) } - val color = ThemeStore.accentColor(requireContext()) - val hexColor = String.format("#%06X", 0xFFFFFF and color) - val appName = HtmlCompat.fromHtml( - "Retro Music", - HtmlCompat.FROM_HTML_MODE_COMPACT - ) - appNameText.text = appName + val appName = resources.getString(titleRes) + binding.appNameText.text = appName } + abstract val titleRes: Int + private fun setUpRecyclerView() { - recyclerView.apply { + binding.recyclerView.apply { layoutManager = this@AbsRecyclerViewFragment.layoutManager adapter = this@AbsRecyclerViewFragment.adapter - val fastScroller = create(this) + create(this) } checkForPadding() } @@ -116,8 +109,8 @@ abstract class AbsRecyclerViewFragment, LM : Recycle } private fun checkIsEmpty() { - emptyText.setText(emptyMessage) - empty.visibility = if (adapter!!.itemCount == 0) View.VISIBLE else View.GONE + binding.emptyText.setText(emptyMessage) + binding.empty.visibility = if (adapter!!.itemCount == 0) View.VISIBLE else View.GONE } private fun checkForPadding() { @@ -125,10 +118,10 @@ abstract class AbsRecyclerViewFragment, LM : Recycle if (itemCount > 0 && MusicPlayerRemote.playingQueue.isNotEmpty()) { val height = DensityUtil.dip2px(requireContext(), 112f) - recyclerView.updatePadding(0, 0, 0, height) + binding.recyclerView.updatePadding(0, 0, 0, height) } else { val height = DensityUtil.dip2px(requireContext(), 56f) - recyclerView.updatePadding(0, 0, 0, height) + binding.recyclerView.updatePadding(0, 0, 0, height) } } @@ -141,15 +134,6 @@ abstract class AbsRecyclerViewFragment, LM : Recycle @NonNull protected abstract fun createAdapter(): A - override fun onOffsetChanged(p0: AppBarLayout?, i: Int) { - /*recyclerView.setPadding( - recyclerView.paddingLeft, - recyclerView.paddingTop, - recyclerView.paddingRight, - i - )*/ - } - override fun onQueueChanged() { super.onQueueChanged() checkForPadding() @@ -162,22 +146,31 @@ abstract class AbsRecyclerViewFragment, LM : Recycle protected fun invalidateLayoutManager() { initLayoutManager() - recyclerView.layoutManager = layoutManager + binding.recyclerView.layoutManager = layoutManager } protected fun invalidateAdapter() { initAdapter() checkIsEmpty() - recyclerView.adapter = adapter + binding.recyclerView.adapter = adapter } fun recyclerView(): RecyclerView { - return recyclerView + return binding.recyclerView + } + + fun getContainer(): CoordinatorLayout { + return binding.root + } + + fun scrollToTop() { + recyclerView().scrollToPosition(0) + binding.appBarLayout.setExpanded(true, true) } override fun onPrepareOptionsMenu(menu: Menu) { super.onPrepareOptionsMenu(menu) - ToolbarContentTintHelper.handleOnPrepareOptionsMenu(requireActivity(), toolbar) + ToolbarContentTintHelper.handleOnPrepareOptionsMenu(requireActivity(), binding.toolbar) } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -185,9 +178,9 @@ abstract class AbsRecyclerViewFragment, LM : Recycle inflater.inflate(R.menu.menu_main, menu) ToolbarContentTintHelper.handleOnCreateOptionsMenu( requireContext(), - toolbar, + binding.toolbar, menu, - ATHToolbarActivity.getToolbarBackgroundColor(toolbar) + ATHToolbarActivity.getToolbarBackgroundColor(binding.toolbar) ) } @@ -209,4 +202,9 @@ abstract class AbsRecyclerViewFragment, LM : Recycle } return super.onOptionsItemSelected(item) } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/folder/FoldersFragment.java b/app/src/main/java/code/name/monkey/retromusic/fragments/folder/FoldersFragment.java index 2f50fe11..d9803ac7 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/folder/FoldersFragment.java +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/folder/FoldersFragment.java @@ -14,13 +14,14 @@ package code.name.monkey.retromusic.fragments.folder; +import static code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackgroundColor; + import android.app.Dialog; import android.content.Context; import android.media.MediaScannerConnection; import android.os.Bundle; import android.os.Environment; import android.text.Html; -import android.text.Spanned; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -30,13 +31,11 @@ import android.view.ViewGroup; import android.view.ViewGroup.MarginLayoutParams; import android.webkit.MimeTypeMap; import android.widget.PopupMenu; -import android.widget.TextView; import android.widget.Toast; +import androidx.activity.OnBackPressedCallback; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.widget.Toolbar; -import androidx.core.text.HtmlCompat; import androidx.loader.app.LoaderManager; import androidx.loader.content.Loader; import androidx.navigation.Navigation; @@ -46,25 +45,36 @@ import androidx.recyclerview.widget.RecyclerView; import com.afollestad.materialcab.MaterialCab; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.snackbar.Snackbar; +import com.google.android.material.transition.MaterialFadeThrough; +import com.google.android.material.transition.MaterialSharedAxis; import org.jetbrains.annotations.NotNull; +import java.io.BufferedReader; import java.io.File; import java.io.FileFilter; +import java.io.FileReader; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.StringTokenizer; import code.name.monkey.appthemehelper.ThemeStore; import code.name.monkey.appthemehelper.util.ATHUtil; import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; +import code.name.monkey.retromusic.App; import code.name.monkey.retromusic.R; import code.name.monkey.retromusic.adapter.SongFileAdapter; +import code.name.monkey.retromusic.adapter.Storage; +import code.name.monkey.retromusic.adapter.StorageAdapter; +import code.name.monkey.retromusic.adapter.StorageClickListener; +import code.name.monkey.retromusic.databinding.FragmentFolderBinding; import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment; import code.name.monkey.retromusic.helper.MusicPlayerRemote; import code.name.monkey.retromusic.helper.menu.SongMenuHelper; @@ -76,6 +86,7 @@ import code.name.monkey.retromusic.misc.DialogAsyncTask; import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener; import code.name.monkey.retromusic.misc.WrappedAsyncTaskLoader; import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.providers.BlacklistStore; import code.name.monkey.retromusic.util.DensityUtil; import code.name.monkey.retromusic.util.FileUtil; import code.name.monkey.retromusic.util.PreferenceUtil; @@ -85,15 +96,14 @@ import code.name.monkey.retromusic.views.BreadCrumbLayout; import code.name.monkey.retromusic.views.ScrollingViewOnApplyWindowInsetsListener; import me.zhanghai.android.fastscroll.FastScroller; -import static code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackgroundColor; - public class FoldersFragment extends AbsMainActivityFragment implements IMainActivityFragmentCallbacks, ICabHolder, BreadCrumbLayout.SelectionCallback, ICallbacks, - LoaderManager.LoaderCallbacks> { + LoaderManager.LoaderCallbacks>, StorageClickListener { + private FragmentFolderBinding binding; public static final String TAG = FoldersFragment.class.getSimpleName(); public static final FileFilter AUDIO_FILE_FILTER = file -> @@ -106,14 +116,9 @@ public class FoldersFragment extends AbsMainActivityFragment private static final String CRUMBS = "crumbs"; private static final int LOADER_ID = 5; private SongFileAdapter adapter; - private Toolbar toolbar; - private TextView appNameText; - private BreadCrumbLayout breadCrumbs; + private StorageAdapter storageAdapter; private MaterialCab cab; - private View coordinatorLayout; - private View empty; - private TextView emojiText; - private Comparator fileComparator = + private final Comparator fileComparator = (lhs, rhs) -> { if (lhs.isDirectory() && !rhs.isDirectory()) { return -1; @@ -123,7 +128,7 @@ public class FoldersFragment extends AbsMainActivityFragment return lhs.getName().compareToIgnoreCase(rhs.getName()); } }; - private RecyclerView recyclerView; + private final ArrayList storageItems = new ArrayList<>(); public FoldersFragment() { super(R.layout.fragment_folder); @@ -158,35 +163,43 @@ public class FoldersFragment extends AbsMainActivityFragment @Override public View onCreateView( @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_folder, container, false); - initViews(view); - return view; + binding = FragmentFolderBinding.inflate(inflater, container, false); + return binding.getRoot(); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + setEnterTransition(new MaterialFadeThrough()); + setExitTransition(new MaterialFadeThrough()); getMainActivity().addMusicServiceEventListener(getLibraryViewModel()); - getMainActivity().setBottomBarVisibility(true); - getMainActivity().setSupportActionBar(toolbar); + getMainActivity().setSupportActionBar(binding.toolbar); getMainActivity().getSupportActionBar().setTitle(null); setStatusBarColorAuto(view); setUpAppbarColor(); setUpBreadCrumbs(); setUpRecyclerView(); + listRoots(); setUpAdapter(); setUpTitle(); + requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + if (!handleBackPress()) { + remove(); + requireActivity().onBackPressed(); + } + } + }); } private void setUpTitle() { - toolbar.setNavigationOnClickListener( - v -> Navigation.findNavController(v).navigate(R.id.searchFragment, null, getNavOptions())); - int color = ThemeStore.Companion.accentColor(requireContext()); - String hexColor = String.format("#%06X", 0xFFFFFF & color); - Spanned appName = - HtmlCompat.fromHtml( - "Retro Music", - HtmlCompat.FROM_HTML_MODE_COMPACT); - appNameText.setText(appName); + binding.toolbar.setNavigationOnClickListener( + v -> { + setExitTransition(new MaterialSharedAxis(MaterialSharedAxis.Z, true).setDuration(300)); + setReenterTransition(new MaterialSharedAxis(MaterialSharedAxis.Z, false).setDuration(300)); + Navigation.findNavController(v).navigate(R.id.searchFragment, null, getNavOptions()); + }); + binding.appNameText.setText(getResources().getString(R.string.folders)); } @Override @@ -194,12 +207,14 @@ public class FoldersFragment extends AbsMainActivityFragment super.onActivityCreated(savedInstanceState); setHasOptionsMenu(true); if (savedInstanceState == null) { + switchToFileAdapter(); setCrumb( new BreadCrumbLayout.Crumb( FileUtil.safeGetCanonicalFile(PreferenceUtil.INSTANCE.getStartDirectory())), true); + } else { - breadCrumbs.restoreFromStateWrapper(savedInstanceState.getParcelable(CRUMBS)); + binding.breadCrumbs.restoreFromStateWrapper(savedInstanceState.getParcelable(CRUMBS)); LoaderManager.getInstance(this).initLoader(LOADER_ID, null, this); } } @@ -213,8 +228,8 @@ public class FoldersFragment extends AbsMainActivityFragment @Override public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); - if (breadCrumbs != null) { - outState.putParcelable(CRUMBS, breadCrumbs.getStateWrapper()); + if (binding != null) { + outState.putParcelable(CRUMBS, binding.breadCrumbs.getStateWrapper()); } } @@ -224,8 +239,8 @@ public class FoldersFragment extends AbsMainActivityFragment cab.finish(); return true; } - if (breadCrumbs != null && breadCrumbs.popHistory()) { - setCrumb(breadCrumbs.lastHistory(), false); + if (binding.breadCrumbs.popHistory()) { + setCrumb(binding.breadCrumbs.lastHistory(), false); return true; } return false; @@ -268,6 +283,9 @@ public class FoldersFragment extends AbsMainActivityFragment new ListSongsAsyncTask.LoadingInfo( toList(file), AUDIO_FILE_FILTER, getFileComparator())); return true; + case R.id.action_add_to_blacklist: + BlacklistStore.getInstance(App.Companion.getContext()).addPath(file); + return true; case R.id.action_set_as_start_directory: PreferenceUtil.INSTANCE.setStartDirectory(file); Toast.makeText( @@ -347,7 +365,7 @@ public class FoldersFragment extends AbsMainActivityFragment } else { final File finalFile = file1; Snackbar.make( - coordinatorLayout, + binding.coordinatorLayout, Html.fromHtml( String.format( getString(R.string.not_listed_in_media_store), file1.getName())), @@ -376,7 +394,7 @@ public class FoldersFragment extends AbsMainActivityFragment @Override public void onLoaderReset(@NonNull Loader> loader) { - updateAdapter(new LinkedList()); + updateAdapter(new LinkedList<>()); } @Override @@ -393,7 +411,7 @@ public class FoldersFragment extends AbsMainActivityFragment @Override public void onPrepareOptionsMenu(@NonNull Menu menu) { super.onPrepareOptionsMenu(menu); - ToolbarContentTintHelper.handleOnPrepareOptionsMenu(requireActivity(), toolbar); + ToolbarContentTintHelper.handleOnPrepareOptionsMenu(requireActivity(), binding.toolbar); } @Override @@ -407,7 +425,7 @@ public class FoldersFragment extends AbsMainActivityFragment menu.removeItem(R.id.action_layout_type); menu.removeItem(R.id.action_sort_order); ToolbarContentTintHelper.handleOnCreateOptionsMenu( - requireContext(), toolbar, menu, getToolbarBackgroundColor(toolbar)); + requireContext(), binding.toolbar, menu, getToolbarBackgroundColor(binding.toolbar)); } @Override @@ -462,26 +480,32 @@ public class FoldersFragment extends AbsMainActivityFragment private void checkForPadding() { final int count = adapter.getItemCount(); - final MarginLayoutParams params = (MarginLayoutParams) coordinatorLayout.getLayoutParams(); - params.bottomMargin = - count > 0 && !MusicPlayerRemote.getPlayingQueue().isEmpty() - ? DensityUtil.dip2px(requireContext(), 104f) - : DensityUtil.dip2px(requireContext(), 54f); + if (binding != null) { + final MarginLayoutParams params = (MarginLayoutParams) binding.coordinatorLayout.getLayoutParams(); + params.bottomMargin = + count > 0 && !MusicPlayerRemote.getPlayingQueue().isEmpty() + ? DensityUtil.dip2px(requireContext(), 104f) + : DensityUtil.dip2px(requireContext(), 54f); + binding.coordinatorLayout.setLayoutParams(params); + } } private void checkIsEmpty() { - emojiText.setText(getEmojiByUnicode(0x1F631)); - if (empty != null) { - empty.setVisibility( + if (binding != null) { + binding.emptyEmoji.setText(getEmojiByUnicode(0x1F631)); + binding.empty.setVisibility( adapter == null || adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); } } @Nullable private BreadCrumbLayout.Crumb getActiveCrumb() { - return breadCrumbs != null && breadCrumbs.size() > 0 - ? breadCrumbs.getCrumb(breadCrumbs.getActiveIndex()) - : null; + if (binding != null) { + return binding.breadCrumbs.size() > 0 + ? binding.breadCrumbs.getCrumb(binding.breadCrumbs.getActiveIndex()) + : null; + } + return null; } private String getEmojiByUnicode(int unicode) { @@ -492,21 +516,11 @@ public class FoldersFragment extends AbsMainActivityFragment return fileComparator; } - private void initViews(View view) { - coordinatorLayout = view.findViewById(R.id.coordinatorLayout); - recyclerView = view.findViewById(R.id.recyclerView); - breadCrumbs = view.findViewById(R.id.breadCrumbs); - empty = view.findViewById(android.R.id.empty); - emojiText = view.findViewById(R.id.emptyEmoji); - toolbar = view.findViewById(R.id.toolbar); - appNameText = view.findViewById(R.id.appNameText); - } - private void saveScrollPosition() { BreadCrumbLayout.Crumb crumb = getActiveCrumb(); if (crumb != null) { crumb.setScrollPosition( - ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition()); + ((LinearLayoutManager) binding.recyclerView.getLayoutManager()).findFirstVisibleItemPosition()); } } @@ -529,46 +543,39 @@ public class FoldersFragment extends AbsMainActivityFragment if (crumb == null) { return; } - saveScrollPosition(); - breadCrumbs.setActiveOrAdd(crumb, false); - if (addToHistory) { - breadCrumbs.addHistory(crumb); + String path = crumb.getFile().getPath(); + if (path.equals("/") || path.equals("/storage") || path.equals("/storage/emulated")) { + switchToStorageAdapter(); + } else { + saveScrollPosition(); + binding.breadCrumbs.setActiveOrAdd(crumb, false); + if (addToHistory) { + binding.breadCrumbs.addHistory(crumb); + } + LoaderManager.getInstance(this).restartLoader(LOADER_ID, null, this); } - LoaderManager.getInstance(this).restartLoader(LOADER_ID, null, this); } private void setUpAdapter() { - adapter = - new SongFileAdapter(getMainActivity(), new LinkedList<>(), R.layout.item_list, this, this); - adapter.registerAdapterDataObserver( - new RecyclerView.AdapterDataObserver() { - @Override - public void onChanged() { - super.onChanged(); - checkIsEmpty(); - checkForPadding(); - } - }); - recyclerView.setAdapter(adapter); - checkIsEmpty(); + switchToFileAdapter(); } private void setUpAppbarColor() { - breadCrumbs.setActivatedContentColor( + binding.breadCrumbs.setActivatedContentColor( ATHUtil.INSTANCE.resolveColor(requireContext(), android.R.attr.textColorPrimary)); - breadCrumbs.setDeactivatedContentColor( + binding.breadCrumbs.setDeactivatedContentColor( ATHUtil.INSTANCE.resolveColor(requireContext(), android.R.attr.textColorSecondary)); } private void setUpBreadCrumbs() { - breadCrumbs.setCallback(this); + binding.breadCrumbs.setCallback(this); } private void setUpRecyclerView() { - recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); - FastScroller fastScroller = ThemedFastScroller.INSTANCE.create(recyclerView); - recyclerView.setOnApplyWindowInsetsListener( - new ScrollingViewOnApplyWindowInsetsListener(recyclerView, fastScroller)); + binding.recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); + FastScroller fastScroller = ThemedFastScroller.INSTANCE.create(binding.recyclerView); + binding.recyclerView.setOnApplyWindowInsetsListener( + new ScrollingViewOnApplyWindowInsetsListener(binding.recyclerView, fastScroller)); } private ArrayList toList(File file) { @@ -580,16 +587,22 @@ public class FoldersFragment extends AbsMainActivityFragment private void updateAdapter(@NonNull List files) { adapter.swapDataSet(files); BreadCrumbLayout.Crumb crumb = getActiveCrumb(); - if (crumb != null && recyclerView != null) { - ((LinearLayoutManager) recyclerView.getLayoutManager()) + if (crumb != null) { + ((LinearLayoutManager) binding.recyclerView.getLayoutManager()) .scrollToPositionWithOffset(crumb.getScrollPosition(), 0); } } + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } + public static class ListPathsAsyncTask extends ListingFilesDialogAsyncTask { - private WeakReference onPathsListedCallbackWeakReference; + private final WeakReference onPathsListedCallbackWeakReference; public ListPathsAsyncTask(Context context, OnPathsListedCallback callback) { super(context); @@ -679,7 +692,7 @@ public class FoldersFragment extends AbsMainActivityFragment private static class AsyncFileLoader extends WrappedAsyncTaskLoader> { - private WeakReference fragmentWeakReference; + private final WeakReference fragmentWeakReference; AsyncFileLoader(FoldersFragment foldersFragment) { super(foldersFragment.requireActivity()); @@ -710,8 +723,8 @@ public class FoldersFragment extends AbsMainActivityFragment extends ListingFilesDialogAsyncTask> { private final Object extra; - private WeakReference callbackWeakReference; - private WeakReference contextWeakReference; + private final WeakReference callbackWeakReference; + private final WeakReference contextWeakReference; ListSongsAsyncTask(Context context, Object extra, OnSongsListedCallback callback) { super(context); @@ -823,4 +836,107 @@ public class FoldersFragment extends AbsMainActivityFragment .create(); } } + + // https://github.com/DrKLO/Telegram/blob/ab221dafadbc17459d78d9ea3e643ae18e934b16/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertDocumentLayout.java#L939 + private void listRoots() { + storageItems.clear(); + HashSet paths = new HashSet<>(); + String defaultPath = Environment.getExternalStorageDirectory().getPath(); + String defaultPathState = Environment.getExternalStorageState(); + if (defaultPathState.equals(Environment.MEDIA_MOUNTED) || defaultPathState.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) { + Storage ext = new Storage(); + if (Environment.isExternalStorageRemovable()) { + ext.title = "SD Card"; + } else { + ext.title = "Internal Storage"; + } + ext.file = Environment.getExternalStorageDirectory(); + storageItems.add(ext); + paths.add(defaultPath); + } + + BufferedReader bufferedReader = null; + try { + bufferedReader = new BufferedReader(new FileReader("/proc/mounts")); + String line; + while ((line = bufferedReader.readLine()) != null) { + if (line.contains("vfat") || line.contains("/mnt")) { + StringTokenizer tokens = new StringTokenizer(line, " "); + tokens.nextToken(); + String path = tokens.nextToken(); + if (paths.contains(path)) { + continue; + } + if (line.contains("/dev/block/vold")) { + if (!line.contains("/mnt/secure") && !line.contains("/mnt/asec") && !line.contains("/mnt/obb") && !line.contains("/dev/mapper") && !line.contains("tmpfs")) { + if (!new File(path).isDirectory()) { + int index = path.lastIndexOf('/'); + if (index != -1) { + String newPath = "/storage/" + path.substring(index + 1); + if (new File(newPath).isDirectory()) { + path = newPath; + } + } + } + paths.add(path); + try { + Storage item = new Storage(); + if (path.toLowerCase().contains("sd")) { + item.title = "SD Card"; + } else { + item.title = "External Storage"; + } + item.file = new File(path); + storageItems.add(item); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (bufferedReader != null) { + try { + bufferedReader.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + + @Override + public void onStorageClicked(@NonNull Storage storage) { + switchToFileAdapter(); + setCrumb( + new BreadCrumbLayout.Crumb( + FileUtil.safeGetCanonicalFile(storage.file)), + true); + } + + public void switchToFileAdapter() { + adapter = + new SongFileAdapter(getMainActivity(), new LinkedList<>(), R.layout.item_list, this, this); + adapter.registerAdapterDataObserver( + new RecyclerView.AdapterDataObserver() { + @Override + public void onChanged() { + super.onChanged(); + checkIsEmpty(); + checkForPadding(); + } + }); + binding.recyclerView.setAdapter(adapter); + checkIsEmpty(); + } + + public void switchToStorageAdapter() { + listRoots(); + storageAdapter = new StorageAdapter(storageItems, this); + binding.recyclerView.setAdapter(storageAdapter); + binding.breadCrumbs.clearCrumbs(); + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenreDetailsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenreDetailsFragment.kt index e226ceed..567df41f 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenreDetailsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenreDetailsFragment.kt @@ -14,28 +14,26 @@ */ package code.name.monkey.retromusic.fragments.genres -import android.graphics.Color import android.os.Bundle import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View import androidx.core.view.ViewCompat +import androidx.core.view.doOnPreDraw import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import code.name.monkey.retromusic.R import code.name.monkey.retromusic.adapter.song.SongAdapter +import code.name.monkey.retromusic.databinding.FragmentPlaylistDetailBinding import code.name.monkey.retromusic.extensions.dipToPix -import code.name.monkey.retromusic.extensions.resolveColor import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment import code.name.monkey.retromusic.helper.menu.GenreMenuHelper import code.name.monkey.retromusic.model.Genre import code.name.monkey.retromusic.model.Song -import com.google.android.material.transition.MaterialArcMotion -import com.google.android.material.transition.MaterialContainerTransform -import kotlinx.android.synthetic.main.fragment_playlist_detail.* +import com.google.android.material.transition.MaterialSharedAxis import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf import java.util.* @@ -47,37 +45,33 @@ class GenreDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playlist_ } private lateinit var genre: Genre private lateinit var songAdapter: SongAdapter - - 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()) - } - } + private var _binding: FragmentPlaylistDetailBinding? = null + private val binding get() = _binding!! override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true).addTarget(view) + returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) + _binding = FragmentPlaylistDetailBinding.bind(view) setHasOptionsMenu(true) - mainActivity.setBottomBarVisibility(false) mainActivity.addMusicServiceEventListener(detailsViewModel) - mainActivity.setSupportActionBar(toolbar) - ViewCompat.setTransitionName(container, "genre") + mainActivity.setSupportActionBar(binding.toolbar) + ViewCompat.setTransitionName(binding.container, "genre") genre = arguments.extraGenre - toolbar?.title = arguments.extraGenre.name + binding.toolbar.title = arguments.extraGenre.name setupRecyclerView() detailsViewModel.getSongs().observe(viewLifecycleOwner, { songs(it) }) - + postponeEnterTransition() + view.doOnPreDraw { + startPostponedEnterTransition() + } } private fun setupRecyclerView() { songAdapter = SongAdapter(requireActivity(), ArrayList(), R.layout.item_list, null) - recyclerView.apply { + binding.recyclerView.apply { itemAnimator = DefaultItemAnimator() layoutManager = LinearLayoutManager(requireContext()) adapter = songAdapter @@ -91,7 +85,7 @@ class GenreDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playlist_ } fun songs(songs: List) { - progressIndicator.hide() + binding.progressIndicator.hide() if (songs.isNotEmpty()) songAdapter.swapDataSet(songs) else songAdapter.swapDataSet(emptyList()) } @@ -102,13 +96,13 @@ class GenreDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playlist_ private fun checkIsEmpty() { checkForPadding() - emptyEmoji.text = getEmojiByUnicode(0x1F631) - empty?.visibility = if (songAdapter.itemCount == 0) View.VISIBLE else View.GONE + binding.emptyEmoji.text = getEmojiByUnicode(0x1F631) + binding.empty.visibility = if (songAdapter.itemCount == 0) View.VISIBLE else View.GONE } private fun checkForPadding() { val height = dipToPix(52f).toInt() - recyclerView.setPadding(0, 0, 0, height) + binding.recyclerView.setPadding(0, 0, 0, height) } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -119,4 +113,9 @@ class GenreDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playlist_ override fun onOptionsItemSelected(item: MenuItem): Boolean { return GenreMenuHelper.handleMenuClick(requireActivity(), genre, item) } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenreDetailsViewModel.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenreDetailsViewModel.kt index 43f941df..113ec34c 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenreDetailsViewModel.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenreDetailsViewModel.kt @@ -60,4 +60,5 @@ class GenreDetailsViewModel( override fun onPlayStateChanged() {} override fun onRepeatModeChanged() {} override fun onShuffleModeChanged() {} + override fun onFavoriteStateChanged() {} } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenresFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenresFragment.kt index 813859aa..de1cb4d6 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenresFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenresFragment.kt @@ -15,25 +15,31 @@ package code.name.monkey.retromusic.fragments.genres import android.os.Bundle +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem import android.view.View import androidx.core.os.bundleOf -import androidx.lifecycle.Observer -import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import code.name.monkey.retromusic.EXTRA_GENRE import code.name.monkey.retromusic.R import code.name.monkey.retromusic.adapter.GenreAdapter +import code.name.monkey.retromusic.fragments.ReloadType import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewFragment import code.name.monkey.retromusic.interfaces.IGenreClickListener import code.name.monkey.retromusic.model.Genre -import com.google.android.material.transition.MaterialElevationScale +import code.name.monkey.retromusic.util.RetroUtil +import com.google.android.gms.cast.framework.CastButtonFactory +import com.google.android.material.transition.MaterialSharedAxis -class GenresFragment : AbsRecyclerViewFragment(), +class +GenresFragment : AbsRecyclerViewFragment(), IGenreClickListener { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - libraryViewModel.getGenre().observe(viewLifecycleOwner, Observer { + libraryViewModel.getGenre().observe(viewLifecycleOwner, { if (it.isNotEmpty()) adapter?.swapDataSet(it) else @@ -42,14 +48,37 @@ class GenresFragment : AbsRecyclerViewFragmentMusic", HtmlCompat.FROM_HTML_MODE_COMPACT ) - appNameText.text = appName + binding.appNameText.text = appName } private fun loadProfile() { - bannerImage?.let { - ProfileBannerGlideRequest.Builder.from( - Glide.with(requireContext()), - ProfileBannerGlideRequest.getBannerModel() - ).build().into(it) + binding.bannerImage?.let { + GlideApp.with(requireContext()) + .asBitmap() + .profileBannerOptions(RetroGlideExtension.getBannerModel()) + .load(RetroGlideExtension.getBannerModel()) + .into(it) } - UserProfileGlideRequest.Builder.from( - Glide.with(requireActivity()), - UserProfileGlideRequest.getUserModel() - ).build().into(userImage) + GlideApp.with(requireActivity()).asBitmap() + .userProfileOptions(RetroGlideExtension.getUserModel()) + .load(RetroGlideExtension.getUserModel()) + .into(binding.userImage) } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -162,10 +169,31 @@ class HomeFragment : menu.findItem(R.id.action_settings).setShowAsAction(SHOW_AS_ACTION_IF_ROOM) ToolbarContentTintHelper.handleOnCreateOptionsMenu( requireContext(), - toolbar, + binding.toolbar, menu, - ATHToolbarActivity.getToolbarBackgroundColor(toolbar) + ATHToolbarActivity.getToolbarBackgroundColor(binding.toolbar) ) + //Setting up cast button + CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.action_cast) + } + + fun scrollToTop() { + binding.container.scrollTo(0, 0) + binding.appBarLayout.setExpanded(true) + } + + fun setSharedAxisXTransitions() { + exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true).apply { + addTarget(binding.root) + } + reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) + } + + private fun setSharedAxisYTransitions() { + exitTransition = MaterialSharedAxis(MaterialSharedAxis.Y, true).apply { + addTarget(binding.root) + } + reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Y, false) } companion object { @@ -199,6 +227,16 @@ class HomeFragment : override fun onPrepareOptionsMenu(menu: Menu) { super.onPrepareOptionsMenu(menu) - ToolbarContentTintHelper.handleOnPrepareOptionsMenu(requireActivity(), toolbar) + ToolbarContentTintHelper.handleOnPrepareOptionsMenu(requireActivity(), binding.toolbar) + } + + override fun onResume() { + super.onResume() + libraryViewModel.fetchHomeSections() + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/library/LibraryFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/library/LibraryFragment.kt index 753a321b..a9d4af02 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/library/LibraryFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/library/LibraryFragment.kt @@ -27,24 +27,32 @@ import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackgroundColor import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentLibraryBinding import code.name.monkey.retromusic.dialogs.CreatePlaylistDialog import code.name.monkey.retromusic.dialogs.ImportPlaylistDialog import code.name.monkey.retromusic.extensions.whichFragment import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment import code.name.monkey.retromusic.model.CategoryInfo -import code.name.monkey.retromusic.state.NowPlayingPanelState import code.name.monkey.retromusic.util.PreferenceUtil -import kotlinx.android.synthetic.main.fragment_library.* +import com.google.android.gms.cast.framework.CastButtonFactory class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) { + private var _binding: FragmentLibraryBinding? = null + private val binding get() = _binding!! + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + _binding = FragmentLibraryBinding.bind(view) + } + override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) setHasOptionsMenu(true) mainActivity.setBottomBarVisibility(true) - mainActivity.setSupportActionBar(toolbar) + mainActivity.setSupportActionBar(binding.toolbar) mainActivity.supportActionBar?.title = null - toolbar.setNavigationOnClickListener { + binding.toolbar.setNavigationOnClickListener { findNavController().navigate( R.id.searchFragment, null, @@ -62,7 +70,7 @@ class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) { "Retro Music", HtmlCompat.FROM_HTML_MODE_COMPACT ) - appNameText.text = appName + binding.appNameText.text = appName } private fun setupNavigationController() { @@ -73,18 +81,18 @@ class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) { val categoryInfo: CategoryInfo = PreferenceUtil.libraryCategory.first { it.visible } if (categoryInfo.visible) { - navGraph.startDestination = categoryInfo.category.id + navGraph.setStartDestination(categoryInfo.category.id) } navController.graph = navGraph NavigationUI.setupWithNavController(mainActivity.getBottomNavigationView(), navController) navController.addOnDestinationChangedListener { _, _, _ -> - appBarLayout.setExpanded(true, true) + binding.appBarLayout.setExpanded(true, true) } } override fun onPrepareOptionsMenu(menu: Menu) { super.onPrepareOptionsMenu(menu) - ToolbarContentTintHelper.handleOnPrepareOptionsMenu(requireActivity(), toolbar) + ToolbarContentTintHelper.handleOnPrepareOptionsMenu(requireActivity(), binding.toolbar) } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -92,10 +100,12 @@ class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) { inflater.inflate(R.menu.menu_main, menu) ToolbarContentTintHelper.handleOnCreateOptionsMenu( requireContext(), - toolbar, + binding.toolbar, menu, - getToolbarBackgroundColor(toolbar) + getToolbarBackgroundColor(binding.toolbar) ) + //Setting up cast button + CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.action_cast) } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -116,4 +126,9 @@ class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) { } return super.onOptionsItemSelected(item) } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/PlayerAlbumCoverFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/PlayerAlbumCoverFragment.kt index 15a962c8..9249fcb7 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/PlayerAlbumCoverFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/PlayerAlbumCoverFragment.kt @@ -14,26 +14,53 @@ */ package code.name.monkey.retromusic.fragments.player +import android.content.SharedPreferences import android.os.Bundle +import android.text.TextUtils import android.view.View +import android.widget.FrameLayout +import android.widget.TextView +import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope +import androidx.preference.PreferenceManager import androidx.viewpager.widget.ViewPager import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.SHOW_LYRICS import code.name.monkey.retromusic.adapter.album.AlbumCoverPagerAdapter import code.name.monkey.retromusic.adapter.album.AlbumCoverPagerAdapter.AlbumCoverFragment +import code.name.monkey.retromusic.databinding.FragmentPlayerAlbumCoverBinding import code.name.monkey.retromusic.fragments.NowPlayingScreen.* import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment +import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.helper.MusicPlayerRemote +import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper +import code.name.monkey.retromusic.model.lyrics.AbsSynchronizedLyrics +import code.name.monkey.retromusic.model.lyrics.Lyrics import code.name.monkey.retromusic.transform.CarousalPagerTransformer import code.name.monkey.retromusic.transform.ParallaxPagerTransformer +import code.name.monkey.retromusic.util.LyricUtil +import code.name.monkey.retromusic.util.NavigationUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import kotlinx.android.synthetic.main.fragment_player_album_cover.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.jaudiotagger.audio.AudioFileIO +import org.jaudiotagger.audio.exceptions.CannotReadException +import org.jaudiotagger.tag.FieldKey +import java.io.File +import java.io.FileNotFoundException class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_player_album_cover), - ViewPager.OnPageChangeListener { + ViewPager.OnPageChangeListener, MusicProgressViewUpdateHelper.Callback, + SharedPreferences.OnSharedPreferenceChangeListener { + private var _binding: FragmentPlayerAlbumCoverBinding? = null + private val binding get() = _binding!! private var callbacks: Callbacks? = null private var currentPosition: Int = 0 + val viewPager get() = binding.viewPager + private val colorReceiver = object : AlbumCoverFragment.ColorReceiver { override fun onColorReady(color: MediaNotificationProcessor, request: Int) { if (currentPosition == request) { @@ -41,64 +68,221 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe } } } + private var progressViewUpdateHelper: MusicProgressViewUpdateHelper? = null + + private val lyricsLayout: FrameLayout get() = binding.playerLyrics + private val lyricsLine1: TextView get() = binding.playerLyricsLine1 + private val lyricsLine2: TextView get() = binding.playerLyricsLine2 + + var lyrics: Lyrics? = null fun removeSlideEffect() { val transformer = ParallaxPagerTransformer(R.id.player_image) transformer.setSpeed(0.3f) } + private fun updateLyrics() { + lyrics = null + lifecycleScope.launch(Dispatchers.IO) { + val song = MusicPlayerRemote.currentSong + val lyrics = try { + var lrcFile: File? = null + if (LyricUtil.isLrcOriginalFileExist(song.data)) { + lrcFile = LyricUtil.getLocalLyricOriginalFile(song.data) + } else if (LyricUtil.isLrcFileExist(song.title, song.artistName)) { + lrcFile = LyricUtil.getLocalLyricFile(song.title, song.artistName) + } + val data: String = LyricUtil.getStringFromLrc(lrcFile) + if (!TextUtils.isEmpty(data)) { + Lyrics.parse(song, data) + } else { + // Get Embedded Lyrics and check if they are Synchronized + val embeddedLyrics = try{ + AudioFileIO.read(File(song.data)).tagOrCreateDefault.getFirst(FieldKey.LYRICS) + } catch(e: Exception){ + null + } + if (AbsSynchronizedLyrics.isSynchronized(embeddedLyrics)) { + Lyrics.parse(song, embeddedLyrics) + } else { + null + } + } + } catch (err: FileNotFoundException) { + null + } catch (e: CannotReadException){ + null + } + withContext(Dispatchers.Main) { + this@PlayerAlbumCoverFragment.lyrics = lyrics + } + } + } + + override fun onUpdateProgressViews(progress: Int, total: Int) { + if (_binding == null) return + + if (!isLyricsLayoutVisible()) { + hideLyricsLayout() + return + } + + if (lyrics !is AbsSynchronizedLyrics) return + val synchronizedLyrics = lyrics as AbsSynchronizedLyrics + + lyricsLayout.visibility = View.VISIBLE + lyricsLayout.alpha = 1f + + val oldLine = lyricsLine2.text.toString() + val line = synchronizedLyrics.getLine(progress) + + if (oldLine != line || oldLine.isEmpty()) { + lyricsLine1.text = oldLine + lyricsLine2.text = line + + lyricsLine1.visibility = View.VISIBLE + lyricsLine2.visibility = View.VISIBLE + + lyricsLine2.measure( + View.MeasureSpec.makeMeasureSpec( + lyricsLine2.measuredWidth, + View.MeasureSpec.EXACTLY + ), + View.MeasureSpec.UNSPECIFIED + ) + val h: Float = lyricsLine2.measuredHeight.toFloat() + + lyricsLine1.alpha = 1f + lyricsLine1.translationY = 0f + lyricsLine1.animate().alpha(0f).translationY(-h).duration = + AbsPlayerFragment.VISIBILITY_ANIM_DURATION + + lyricsLine2.alpha = 0f + lyricsLine2.translationY = h + lyricsLine2.animate().alpha(1f).translationY(0f).duration = + AbsPlayerFragment.VISIBILITY_ANIM_DURATION + } + } + + private fun isLyricsLayoutVisible(): Boolean { + return lyrics != null && lyrics!!.isSynchronized && lyrics!!.isValid + } + + private fun hideLyricsLayout() { + lyricsLayout.animate().alpha(0f).setDuration(AbsPlayerFragment.VISIBILITY_ANIM_DURATION) + .withEndAction { + if (_binding == null) return@withEndAction + lyricsLayout.visibility = View.GONE + lyricsLine1.text = null + lyricsLine2.text = null + } + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - viewPager.addOnPageChangeListener(this) + _binding = FragmentPlayerAlbumCoverBinding.bind(view) + binding.viewPager.addOnPageChangeListener(this) val nps = PreferenceUtil.nowPlayingScreen val metrics = resources.displayMetrics val ratio = metrics.heightPixels.toFloat() / metrics.widthPixels.toFloat() if (nps == Full || nps == Classic || nps == Fit || nps == Gradient) { - viewPager.offscreenPageLimit = 2 + binding.viewPager.offscreenPageLimit = 2 } else if (PreferenceUtil.isCarouselEffect) { - viewPager.clipToPadding = false + binding.viewPager.clipToPadding = false val padding = if (ratio >= 1.777f) { 40 } else { 100 } - viewPager.setPadding(padding, 0, padding, 0) - viewPager.pageMargin = 0 - viewPager.setPageTransformer(false, CarousalPagerTransformer(requireContext())) + binding.viewPager.setPadding(padding, 0, padding, 0) + binding.viewPager.pageMargin = 0 + binding.viewPager.setPageTransformer(false, CarousalPagerTransformer(requireContext())) } else { - viewPager.offscreenPageLimit = 2 - viewPager.setPageTransformer( + binding.viewPager.offscreenPageLimit = 2 + binding.viewPager.setPageTransformer( true, PreferenceUtil.albumCoverTransform ) } + progressViewUpdateHelper = MusicProgressViewUpdateHelper(this, 500, 1000) + // Don't show lyrics container for below conditions + if (!(nps == Circle || nps == Peak || nps == Tiny || !PreferenceUtil.showLyrics)) { + progressViewUpdateHelper?.start() + } + // Go to lyrics activity when clicked lyrics + binding.playerLyricsLine2.setOnClickListener { + NavigationUtil.goToLyrics(requireActivity()) + } + } + + override fun onResume() { + super.onResume() + val nps = PreferenceUtil.nowPlayingScreen + // Don't show lyrics container for below conditions + if (nps == Circle || nps == Peak || nps == Tiny || !PreferenceUtil.showLyrics) { + lyricsLayout.isVisible = false + progressViewUpdateHelper?.stop() + } else { + lyricsLayout.isVisible = true + progressViewUpdateHelper?.start() + } + PreferenceManager.getDefaultSharedPreferences(requireContext()) + .registerOnSharedPreferenceChangeListener(this) } override fun onDestroyView() { super.onDestroyView() - viewPager.removeOnPageChangeListener(this) + PreferenceManager.getDefaultSharedPreferences(requireContext()) + .unregisterOnSharedPreferenceChangeListener(this) + binding.viewPager.removeOnPageChangeListener(this) + progressViewUpdateHelper?.stop() + _binding = null } override fun onServiceConnected() { updatePlayingQueue() + updateLyrics() } override fun onPlayingMetaChanged() { - viewPager.currentItem = MusicPlayerRemote.position + binding.viewPager.currentItem = MusicPlayerRemote.position + updateLyrics() } override fun onQueueChanged() { updatePlayingQueue() } + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) { + if (key == SHOW_LYRICS) { + if (sharedPreferences.getBoolean(key, false)) { + progressViewUpdateHelper?.start() + lyricsLayout.animate().alpha(1f).duration = + AbsPlayerFragment.VISIBILITY_ANIM_DURATION + binding.playerLyrics.isVisible = true + } else { + progressViewUpdateHelper?.stop() + lyricsLayout.animate().alpha(0f) + .setDuration(AbsPlayerFragment.VISIBILITY_ANIM_DURATION) + .withEndAction { + if (_binding != null) { + binding.playerLyrics.isVisible = false + lyricsLine1.text = null + lyricsLine2.text = null + } + } + } + } + } + private fun updatePlayingQueue() { - viewPager.apply { + binding.viewPager.apply { adapter = AlbumCoverPagerAdapter(childFragmentManager, MusicPlayerRemote.playingQueue) - viewPager.adapter!!.notifyDataSetChanged() - viewPager.currentItem = MusicPlayerRemote.position + adapter!!.notifyDataSetChanged() + currentItem = MusicPlayerRemote.position onPageSelected(MusicPlayerRemote.position) } } @@ -108,8 +292,11 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe override fun onPageSelected(position: Int) { currentPosition = position - if (viewPager.adapter != null) { - (viewPager.adapter as AlbumCoverPagerAdapter).receiveColor(colorReceiver, position) + if (binding.viewPager.adapter != null) { + (binding.viewPager.adapter as AlbumCoverPagerAdapter).receiveColor( + colorReceiver, + position + ) } if (position != MusicPlayerRemote.position) { MusicPlayerRemote.playSongAt(position) @@ -119,6 +306,7 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe override fun onPageScrollStateChanged(state: Int) { } + private fun notifyColorChange(color: MediaNotificationProcessor) { callbacks?.onColorChanged(color) } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/adaptive/AdaptiveFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/adaptive/AdaptiveFragment.kt index 7978c0ea..34f8baf5 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/adaptive/AdaptiveFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/adaptive/AdaptiveFragment.kt @@ -20,6 +20,7 @@ import androidx.appcompat.widget.Toolbar import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentAdaptivePlayerBinding import code.name.monkey.retromusic.extensions.surfaceColor import code.name.monkey.retromusic.extensions.textColorPrimary import code.name.monkey.retromusic.extensions.textColorSecondary @@ -28,12 +29,13 @@ import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import kotlinx.android.synthetic.main.fragment_adaptive_player.* class AdaptiveFragment : AbsPlayerFragment(R.layout.fragment_adaptive_player) { + private var _binding: FragmentAdaptivePlayerBinding? = null + private val binding get() = _binding!! override fun playerToolbar(): Toolbar { - return playerToolbar + return binding.playerToolbar } private var lastColor: Int = 0 @@ -41,6 +43,7 @@ class AdaptiveFragment : AbsPlayerFragment(R.layout.fragment_adaptive_player) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentAdaptivePlayerBinding.bind(view) setUpSubFragments() setUpPlayerToolbar() } @@ -57,7 +60,7 @@ class AdaptiveFragment : AbsPlayerFragment(R.layout.fragment_adaptive_player) { } private fun setUpPlayerToolbar() { - playerToolbar.apply { + binding.playerToolbar.apply { inflateMenu(R.menu.menu_player) setNavigationOnClickListener { requireActivity().onBackPressed() } ToolbarContentTintHelper.colorizeToolbar(this, surfaceColor(), requireActivity()) @@ -80,7 +83,7 @@ class AdaptiveFragment : AbsPlayerFragment(R.layout.fragment_adaptive_player) { private fun updateSong() { val song = MusicPlayerRemote.currentSong - playerToolbar.apply { + binding.playerToolbar.apply { title = song.title subtitle = song.artistName } @@ -102,7 +105,7 @@ class AdaptiveFragment : AbsPlayerFragment(R.layout.fragment_adaptive_player) { lastColor = color.primaryTextColor libraryViewModel.updateColor(color.primaryTextColor) ToolbarContentTintHelper.colorizeToolbar( - playerToolbar, + binding.playerToolbar, ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal), requireActivity() ) @@ -119,6 +122,11 @@ class AdaptiveFragment : AbsPlayerFragment(R.layout.fragment_adaptive_player) { return false } + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + override fun toolbarIconColor(): Int { return ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal) } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/adaptive/AdaptivePlaybackControlsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/adaptive/AdaptivePlaybackControlsFragment.kt index df01a4c2..a1196a03 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/adaptive/AdaptivePlaybackControlsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/adaptive/AdaptivePlaybackControlsFragment.kt @@ -28,6 +28,7 @@ import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentAdaptivePlayerPlaybackControlsBinding import code.name.monkey.retromusic.extensions.applyColor import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.ripAlpha @@ -41,7 +42,6 @@ import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import kotlinx.android.synthetic.main.fragment_adaptive_player_playback_controls.* class AdaptivePlaybackControlsFragment : AbsPlayerControlsFragment(R.layout.fragment_adaptive_player_playback_controls) { @@ -49,6 +49,8 @@ class AdaptivePlaybackControlsFragment : private var lastPlaybackControlsColor: Int = 0 private var lastDisabledPlaybackControlsColor: Int = 0 private var progressViewUpdateHelper: MusicProgressViewUpdateHelper? = null + private var _binding: FragmentAdaptivePlayerPlaybackControlsBinding? = null + private val binding get() = _binding!! override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -59,34 +61,31 @@ class AdaptivePlaybackControlsFragment : inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { - return inflater.inflate( - R.layout.fragment_adaptive_player_playback_controls, - container, - false - ) + ): View { + _binding = FragmentAdaptivePlayerPlaybackControlsBinding.inflate(inflater, container, false) + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setUpMusicControllers() - playPauseButton.setOnClickListener { + binding.playPauseButton.setOnClickListener { if (MusicPlayerRemote.isPlaying) { MusicPlayerRemote.pauseSong() } else { MusicPlayerRemote.resumePlaying() } - showBonceAnimation(playPauseButton) + showBounceAnimation(binding.playPauseButton) } } private fun updateSong() { if (PreferenceUtil.isSongInfo) { - songInfo?.text = getSongInfo(MusicPlayerRemote.currentSong) - songInfo.show() + binding.songInfo.text = getSongInfo(MusicPlayerRemote.currentSong) + binding.songInfo.show() } else { - songInfo?.hide() + binding.songInfo.hide() } } @@ -153,12 +152,12 @@ class AdaptivePlaybackControlsFragment : }.ripAlpha() TintHelper.setTintAuto( - playPauseButton, + binding.playPauseButton, MaterialValueHelper.getPrimaryTextColor(context, ColorUtil.isColorLight(colorFinal)), false ) - TintHelper.setTintAuto(playPauseButton, colorFinal, true) - progressSlider.applyColor(colorFinal) + TintHelper.setTintAuto(binding.playPauseButton, colorFinal, true) + binding.progressSlider.applyColor(colorFinal) volumeFragment?.setTintable(colorFinal) } @@ -167,14 +166,14 @@ class AdaptivePlaybackControlsFragment : } private fun setUpPlayPauseFab() { - playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) + binding.playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) } private fun updatePlayPauseDrawableState() { if (MusicPlayerRemote.isPlaying) { - playPauseButton.setImageResource(R.drawable.ic_pause) + binding.playPauseButton.setImageResource(R.drawable.ic_pause) } else { - playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_32dp) + binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_32dp) } } @@ -188,17 +187,17 @@ class AdaptivePlaybackControlsFragment : private fun setUpPrevNext() { updatePrevNextColor() - nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } - previousButton.setOnClickListener { MusicPlayerRemote.back() } + binding.nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } + binding.previousButton.setOnClickListener { MusicPlayerRemote.back() } } private fun updatePrevNextColor() { - nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) - previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) } private fun setUpShuffleButton() { - shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } + binding.shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } } override fun show() { @@ -209,11 +208,11 @@ class AdaptivePlaybackControlsFragment : override fun updateShuffleState() { when (MusicPlayerRemote.shuffleMode) { - MusicService.SHUFFLE_MODE_SHUFFLE -> shuffleButton.setColorFilter( + MusicService.SHUFFLE_MODE_SHUFFLE -> binding.shuffleButton.setColorFilter( lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) - else -> shuffleButton.setColorFilter( + else -> binding.shuffleButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) @@ -221,31 +220,37 @@ class AdaptivePlaybackControlsFragment : } private fun setUpRepeatButton() { - repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } + binding.repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } } override fun updateRepeatState() { when (MusicPlayerRemote.repeatMode) { MusicService.REPEAT_MODE_NONE -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter( + binding.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.repeatButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) } MusicService.REPEAT_MODE_ALL -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } MusicService.REPEAT_MODE_THIS -> { - repeatButton.setImageResource(R.drawable.ic_repeat_one) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat_one) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } } } override fun setUpProgressSlider() { - progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { + binding.progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) { MusicPlayerRemote.seekTo(progress) @@ -259,14 +264,19 @@ class AdaptivePlaybackControlsFragment : } override fun onUpdateProgressViews(progress: Int, total: Int) { - progressSlider.max = total + binding.progressSlider.max = total - val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) + val animator = ObjectAnimator.ofInt(binding.progressSlider, "progress", progress) animator.duration = SLIDER_ANIMATION_TIME animator.interpolator = LinearInterpolator() animator.start() - songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) - songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) + binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/blur/BlurPlaybackControlsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/blur/BlurPlaybackControlsFragment.kt index 4cccb54c..55f8e322 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/blur/BlurPlaybackControlsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/blur/BlurPlaybackControlsFragment.kt @@ -28,9 +28,12 @@ import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentBlurPlayerPlaybackControlsBinding import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment +import code.name.monkey.retromusic.fragments.base.goToAlbum +import code.name.monkey.retromusic.fragments.base.goToArtist import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler @@ -39,11 +42,11 @@ import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import kotlinx.android.synthetic.main.fragment_blur_player_playback_controls.* class BlurPlaybackControlsFragment : AbsPlayerControlsFragment(R.layout.fragment_blur_player_playback_controls) { - + private var _binding: FragmentBlurPlayerPlaybackControlsBinding? = null + private val binding get() = _binding!! private var lastPlaybackControlsColor: Int = 0 private var lastDisabledPlaybackControlsColor: Int = 0 private var progressViewUpdateHelper: MusicProgressViewUpdateHelper? = null @@ -55,30 +58,37 @@ class BlurPlaybackControlsFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentBlurPlayerPlaybackControlsBinding.bind(view) setUpMusicControllers() - playPauseButton.setOnClickListener { + binding.playPauseButton.setOnClickListener { if (MusicPlayerRemote.isPlaying) { MusicPlayerRemote.pauseSong() } else { MusicPlayerRemote.resumePlaying() } - showBonceAnimation() + showBounceAnimation() + } + binding.title.isSelected = true + binding.text.isSelected = true + binding.title.setOnClickListener { + goToAlbum(requireActivity()) + } + binding.text.setOnClickListener { + goToArtist(requireActivity()) } - title.isSelected = true - text.isSelected = true } private fun updateSong() { val song = MusicPlayerRemote.currentSong - title.text = song.title - text.text = String.format("%s • %s", song.artistName, song.albumName) + binding.title.text = song.title + binding.text.text = String.format("%s • %s", song.artistName, song.albumName) if (PreferenceUtil.isSongInfo) { - songInfo.show() - songInfo?.text = getSongInfo(song) + binding.songInfo.show() + binding.songInfo.text = getSongInfo(song) } else { - songInfo?.hide() + binding.songInfo.hide() } } @@ -121,41 +131,41 @@ class BlurPlaybackControlsFragment : lastDisabledPlaybackControlsColor = ContextCompat.getColor(requireContext(), R.color.md_grey_500) - title.setTextColor(lastPlaybackControlsColor) + binding.title.setTextColor(lastPlaybackControlsColor) - songCurrentProgress.setTextColor(lastPlaybackControlsColor) - songTotalTime.setTextColor(lastPlaybackControlsColor) + binding.songCurrentProgress.setTextColor(lastPlaybackControlsColor) + binding.songTotalTime.setTextColor(lastPlaybackControlsColor) updateRepeatState() updateShuffleState() updatePrevNextColor() - text.setTextColor(lastDisabledPlaybackControlsColor) - songInfo.setTextColor(lastDisabledPlaybackControlsColor) + binding.text.setTextColor(lastPlaybackControlsColor) + binding.songInfo.setTextColor(lastDisabledPlaybackControlsColor) - TintHelper.setTintAuto(progressSlider, lastPlaybackControlsColor, false) + TintHelper.setTintAuto(binding.progressSlider, lastPlaybackControlsColor, false) volumeFragment?.setTintableColor(lastPlaybackControlsColor) setFabColor(lastPlaybackControlsColor) } private fun setFabColor(i: Int) { TintHelper.setTintAuto( - playPauseButton, + binding.playPauseButton, MaterialValueHelper.getPrimaryTextColor(context, ColorUtil.isColorLight(i)), false ) - TintHelper.setTintAuto(playPauseButton, i, true) + TintHelper.setTintAuto(binding.playPauseButton, i, true) } private fun setUpPlayPauseFab() { - playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) + binding.playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) } private fun updatePlayPauseDrawableState() { if (MusicPlayerRemote.isPlaying) { - playPauseButton.setImageResource(R.drawable.ic_pause) + binding.playPauseButton.setImageResource(R.drawable.ic_pause) } else { - playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_32dp) + binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_32dp) } } @@ -169,26 +179,26 @@ class BlurPlaybackControlsFragment : private fun setUpPrevNext() { updatePrevNextColor() - nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } - previousButton.setOnClickListener { MusicPlayerRemote.back() } + binding.nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } + binding.previousButton.setOnClickListener { MusicPlayerRemote.back() } } private fun updatePrevNextColor() { - nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) - previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) } private fun setUpShuffleButton() { - shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } + binding.shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } } override fun updateShuffleState() { when (MusicPlayerRemote.shuffleMode) { - MusicService.SHUFFLE_MODE_SHUFFLE -> shuffleButton.setColorFilter( + MusicService.SHUFFLE_MODE_SHUFFLE -> binding.shuffleButton.setColorFilter( lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) - else -> shuffleButton.setColorFilter( + else -> binding.shuffleButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) @@ -196,31 +206,37 @@ class BlurPlaybackControlsFragment : } private fun setUpRepeatButton() { - repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } + binding.repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } } override fun updateRepeatState() { when (MusicPlayerRemote.repeatMode) { MusicService.REPEAT_MODE_NONE -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter( + binding.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.repeatButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) } MusicService.REPEAT_MODE_ALL -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } MusicService.REPEAT_MODE_THIS -> { - repeatButton.setImageResource(R.drawable.ic_repeat_one) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat_one) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } } } public override fun show() { - playPauseButton!!.animate() + binding.playPauseButton.animate() .scaleX(1f) .scaleY(1f) .rotation(360f) @@ -229,17 +245,15 @@ class BlurPlaybackControlsFragment : } public override fun hide() { - if (playPauseButton != null) { - playPauseButton!!.apply { - scaleX = 0f - scaleY = 0f - rotation = 0f - } + binding.playPauseButton.apply { + scaleX = 0f + scaleY = 0f + rotation = 0f } } - private fun showBonceAnimation() { - playPauseButton.apply { + private fun showBounceAnimation() { + binding.playPauseButton.apply { clearAnimation() scaleX = 0.9f scaleY = 0.9f @@ -262,7 +276,7 @@ class BlurPlaybackControlsFragment : } override fun setUpProgressSlider() { - progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { + binding.progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) { MusicPlayerRemote.seekTo(progress) @@ -276,14 +290,19 @@ class BlurPlaybackControlsFragment : } override fun onUpdateProgressViews(progress: Int, total: Int) { - progressSlider.max = total + binding.progressSlider.max = total - val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) + val animator = ObjectAnimator.ofInt(binding.progressSlider, "progress", progress) animator.duration = SLIDER_ANIMATION_TIME animator.interpolator = LinearInterpolator() animator.start() - songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) - songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) + binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/blur/BlurPlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/blur/BlurPlayerFragment.kt index e08e441b..a4f3df43 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/blur/BlurPlayerFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/blur/BlurPlayerFragment.kt @@ -23,30 +23,35 @@ import androidx.preference.PreferenceManager import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.retromusic.NEW_BLUR_AMOUNT import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentBlurBinding import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.glide.BlurTransformation +import code.name.monkey.retromusic.glide.GlideApp +import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.RetroMusicColoredTarget -import code.name.monkey.retromusic.glide.SongGlideRequest import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import com.bumptech.glide.Glide -import kotlinx.android.synthetic.main.fragment_blur.* class BlurPlayerFragment : AbsPlayerFragment(R.layout.fragment_blur), SharedPreferences.OnSharedPreferenceChangeListener { override fun playerToolbar(): Toolbar { - return playerToolbar + return binding.playerToolbar } private lateinit var playbackControlsFragment: BlurPlaybackControlsFragment private var lastColor: Int = 0 + private var _binding: FragmentBlurBinding? = null + private val binding get() = _binding!! + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentBlurBinding.bind(view) setUpSubFragments() setUpPlayerToolbar() } @@ -60,7 +65,7 @@ class BlurPlayerFragment : AbsPlayerFragment(R.layout.fragment_blur), } private fun setUpPlayerToolbar() { - playerToolbar.apply { + binding.playerToolbar.apply { inflateMenu(R.menu.menu_player) setNavigationOnClickListener { requireActivity().onBackPressed() } ToolbarContentTintHelper.colorizeToolbar(this, Color.WHITE, activity) @@ -75,7 +80,7 @@ class BlurPlayerFragment : AbsPlayerFragment(R.layout.fragment_blur), playbackControlsFragment.setColor(color) lastColor = color.backgroundColor libraryViewModel.updateColor(color.backgroundColor) - ToolbarContentTintHelper.colorizeToolbar(playerToolbar, Color.WHITE, activity) + ToolbarContentTintHelper.colorizeToolbar(binding.playerToolbar, Color.WHITE, activity) } override fun toggleFavorite(song: Song) { @@ -105,20 +110,20 @@ class BlurPlayerFragment : AbsPlayerFragment(R.layout.fragment_blur), private fun updateBlur() { val blurAmount = PreferenceManager.getDefaultSharedPreferences(requireContext()) .getInt(NEW_BLUR_AMOUNT, 25) - colorBackground.clearColorFilter() - SongGlideRequest.Builder.from(Glide.with(requireActivity()), MusicPlayerRemote.currentSong) - .checkIgnoreMediaStore(requireContext()) - .generatePalette(requireContext()).build() + binding.colorBackground.clearColorFilter() + GlideApp.with(requireActivity()).asBitmapPalette() + .songCoverOptions(MusicPlayerRemote.currentSong) + .load(RetroGlideExtension.getSongModel(MusicPlayerRemote.currentSong)) .dontAnimate() .transform( BlurTransformation.Builder(requireContext()) .blurRadius(blurAmount.toFloat()) .build() ) - .into(object : RetroMusicColoredTarget(colorBackground) { + .into(object : RetroMusicColoredTarget(binding.colorBackground) { override fun onColorReady(colors: MediaNotificationProcessor) { if (colors.backgroundColor == defaultFooterColor) { - colorBackground.setColorFilter(colors.backgroundColor) + binding.colorBackground.setColorFilter(colors.backgroundColor) } } }) @@ -144,6 +149,7 @@ class BlurPlayerFragment : AbsPlayerFragment(R.layout.fragment_blur), super.onDestroyView() PreferenceManager.getDefaultSharedPreferences(requireContext()) .unregisterOnSharedPreferenceChangeListener(this) + _binding = null } override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/card/CardFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/card/CardFragment.kt index 01d586b6..698bf513 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/card/CardFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/card/CardFragment.kt @@ -20,17 +20,17 @@ import android.view.View import androidx.appcompat.widget.Toolbar import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentCardPlayerBinding import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.fragments.player.normal.PlayerFragment import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import kotlinx.android.synthetic.main.fragment_card_player.* class CardFragment : AbsPlayerFragment(R.layout.fragment_card_player) { override fun playerToolbar(): Toolbar { - return playerToolbar + return binding.playerToolbar } private var lastColor: Int = 0 @@ -38,6 +38,9 @@ class CardFragment : AbsPlayerFragment(R.layout.fragment_card_player) { get() = lastColor private lateinit var playbackControlsFragment: CardPlaybackControlsFragment + private var _binding: FragmentCardPlayerBinding? = null + private val binding get() = _binding!! + override fun onShow() { playbackControlsFragment.show() @@ -60,7 +63,7 @@ class CardFragment : AbsPlayerFragment(R.layout.fragment_card_player) { playbackControlsFragment.setColor(color) lastColor = color.primaryTextColor libraryViewModel.updateColor(color.primaryTextColor) - ToolbarContentTintHelper.colorizeToolbar(playerToolbar, Color.WHITE, activity) + ToolbarContentTintHelper.colorizeToolbar(binding.playerToolbar, Color.WHITE, activity) } override fun toggleFavorite(song: Song) { @@ -76,6 +79,7 @@ class CardFragment : AbsPlayerFragment(R.layout.fragment_card_player) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentCardPlayerBinding.bind(view) setUpSubFragments() setUpPlayerToolbar() } @@ -90,11 +94,11 @@ class CardFragment : AbsPlayerFragment(R.layout.fragment_card_player) { } private fun setUpPlayerToolbar() { - playerToolbar.inflateMenu(R.menu.menu_player) - playerToolbar.setNavigationOnClickListener { requireActivity().onBackPressed() } - playerToolbar.setOnMenuItemClickListener(this) + binding.playerToolbar.inflateMenu(R.menu.menu_player) + binding.playerToolbar.setNavigationOnClickListener { requireActivity().onBackPressed() } + binding.playerToolbar.setOnMenuItemClickListener(this) - ToolbarContentTintHelper.colorizeToolbar(playerToolbar, Color.WHITE, activity) + ToolbarContentTintHelper.colorizeToolbar(binding.playerToolbar, Color.WHITE, activity) } override fun onServiceConnected() { @@ -105,6 +109,11 @@ class CardFragment : AbsPlayerFragment(R.layout.fragment_card_player) { updateIsFavorite() } + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + companion object { fun newInstance(): PlayerFragment { diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/card/CardPlaybackControlsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/card/CardPlaybackControlsFragment.kt index 3ec3f299..a142ebaf 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/card/CardPlaybackControlsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/card/CardPlaybackControlsFragment.kt @@ -26,10 +26,13 @@ import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentCardPlayerPlaybackControlsBinding import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.ripAlpha import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment +import code.name.monkey.retromusic.fragments.base.goToAlbum +import code.name.monkey.retromusic.fragments.base.goToArtist import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler @@ -38,8 +41,6 @@ import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import kotlinx.android.synthetic.main.fragment_card_player_playback_controls.* -import kotlinx.android.synthetic.main.media_button.* class CardPlaybackControlsFragment : AbsPlayerControlsFragment(R.layout.fragment_card_player_playback_controls) { @@ -47,6 +48,9 @@ class CardPlaybackControlsFragment : private var lastPlaybackControlsColor: Int = 0 private var lastDisabledPlaybackControlsColor: Int = 0 private var progressViewUpdateHelper: MusicProgressViewUpdateHelper? = null + private var _binding: FragmentCardPlayerPlaybackControlsBinding? = null + private val binding get() = _binding!! + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -55,30 +59,37 @@ class CardPlaybackControlsFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentCardPlayerPlaybackControlsBinding.bind(view) setUpMusicControllers() - playPauseButton.setOnClickListener { + binding.mediaButton.playPauseButton.setOnClickListener { if (MusicPlayerRemote.isPlaying) { MusicPlayerRemote.pauseSong() } else { MusicPlayerRemote.resumePlaying() } - showBonceAnimation(playPauseButton) + showBounceAnimation(binding.mediaButton.playPauseButton) + } + binding.title.isSelected = true + binding.text.isSelected = true + binding.title.setOnClickListener { + goToAlbum(requireActivity()) + } + binding.text.setOnClickListener { + goToArtist(requireActivity()) } - title.isSelected = true - text.isSelected = true } private fun updateSong() { val song = MusicPlayerRemote.currentSong - title.text = song.title - text.text = song.artistName + binding.title.text = song.title + binding.text.text = song.artistName if (PreferenceUtil.isSongInfo) { - songInfo.text = getSongInfo(MusicPlayerRemote.currentSong) - songInfo.show() + binding.songInfo.text = getSongInfo(MusicPlayerRemote.currentSong) + binding.songInfo.show() } else { - songInfo.hide() + binding.songInfo.hide() } } @@ -139,13 +150,13 @@ class CardPlaybackControlsFragment : } else { ThemeStore.accentColor(requireContext()).ripAlpha() } - image.setColorFilter(colorFinal, PorterDuff.Mode.SRC_IN) + binding.image.setColorFilter(colorFinal, PorterDuff.Mode.SRC_IN) TintHelper.setTintAuto( - playPauseButton, + binding.mediaButton.playPauseButton, MaterialValueHelper.getPrimaryTextColor(context, ColorUtil.isColorLight(colorFinal)), false ) - TintHelper.setTintAuto(playPauseButton, colorFinal, true) + TintHelper.setTintAuto(binding.mediaButton.playPauseButton, colorFinal, true) volumeFragment?.setTintable(colorFinal) } @@ -155,14 +166,14 @@ class CardPlaybackControlsFragment : } private fun setUpPlayPauseFab() { - playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) + binding.mediaButton.playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) } private fun updatePlayPauseDrawableState() { if (MusicPlayerRemote.isPlaying) { - playPauseButton.setImageResource(R.drawable.ic_pause) + binding.mediaButton.playPauseButton.setImageResource(R.drawable.ic_pause) } else { - playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_32dp) + binding.mediaButton.playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_32dp) } } @@ -176,26 +187,32 @@ class CardPlaybackControlsFragment : private fun setUpPrevNext() { updatePrevNextColor() - nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } - previousButton.setOnClickListener { MusicPlayerRemote.back() } + binding.mediaButton.nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } + binding.mediaButton.previousButton.setOnClickListener { MusicPlayerRemote.back() } } private fun updatePrevNextColor() { - nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) - previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.mediaButton.nextButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) + binding.mediaButton.previousButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } private fun setUpShuffleButton() { - shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } + binding.mediaButton.shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } } override fun updateShuffleState() { when (MusicPlayerRemote.shuffleMode) { - MusicService.SHUFFLE_MODE_SHUFFLE -> shuffleButton.setColorFilter( + MusicService.SHUFFLE_MODE_SHUFFLE -> binding.mediaButton.shuffleButton.setColorFilter( lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) - else -> shuffleButton.setColorFilter( + else -> binding.mediaButton.shuffleButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) @@ -203,45 +220,51 @@ class CardPlaybackControlsFragment : } private fun setUpRepeatButton() { - repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } + binding.mediaButton.repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } } override fun updateRepeatState() { when (MusicPlayerRemote.repeatMode) { MusicService.REPEAT_MODE_NONE -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter( + binding.mediaButton.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.mediaButton.repeatButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) } MusicService.REPEAT_MODE_ALL -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.mediaButton.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.mediaButton.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } MusicService.REPEAT_MODE_THIS -> { - repeatButton.setImageResource(R.drawable.ic_repeat_one) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.mediaButton.repeatButton.setImageResource(R.drawable.ic_repeat_one) + binding.mediaButton.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } } } override fun onUpdateProgressViews(progress: Int, total: Int) { - progressSlider.max = total + binding.progressSlider.max = total - val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) + val animator = ObjectAnimator.ofInt(binding.progressSlider, "progress", progress) animator.duration = SLIDER_ANIMATION_TIME animator.interpolator = LinearInterpolator() animator.start() - songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) - songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) + binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) } private fun updateProgressTextColor() { val color = MaterialValueHelper.getPrimaryTextColor(context, false) - songTotalTime!!.setTextColor(color) - songCurrentProgress!!.setTextColor(color) + binding.songTotalTime.setTextColor(color) + binding.songCurrentProgress.setTextColor(color) } public override fun show() { @@ -253,7 +276,7 @@ class CardPlaybackControlsFragment : } override fun setUpProgressSlider() { - progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { + binding.progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) { MusicPlayerRemote.seekTo(progress) @@ -265,4 +288,10 @@ class CardPlaybackControlsFragment : } }) } + + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/cardblur/CardBlurFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/cardblur/CardBlurFragment.kt index 669661c5..5c5432a8 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/cardblur/CardBlurFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/cardblur/CardBlurFragment.kt @@ -23,22 +23,22 @@ import androidx.preference.PreferenceManager import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.retromusic.NEW_BLUR_AMOUNT import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentCardBlurPlayerBinding import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.fragments.player.normal.PlayerFragment import code.name.monkey.retromusic.glide.BlurTransformation +import code.name.monkey.retromusic.glide.GlideApp +import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.RetroMusicColoredTarget -import code.name.monkey.retromusic.glide.SongGlideRequest import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import com.bumptech.glide.Glide -import kotlinx.android.synthetic.main.fragment_card_blur_player.* class CardBlurFragment : AbsPlayerFragment(R.layout.fragment_card_blur_player), SharedPreferences.OnSharedPreferenceChangeListener { override fun playerToolbar(): Toolbar { - return playerToolbar + return binding.playerToolbar } private var lastColor: Int = 0 @@ -46,6 +46,9 @@ class CardBlurFragment : AbsPlayerFragment(R.layout.fragment_card_blur_player), get() = lastColor private lateinit var playbackControlsFragment: CardBlurPlaybackControlsFragment + private var _binding: FragmentCardBlurPlayerBinding? = null + private val binding get() = _binding!! + override fun onShow() { playbackControlsFragment.show() } @@ -67,10 +70,10 @@ class CardBlurFragment : AbsPlayerFragment(R.layout.fragment_card_blur_player), playbackControlsFragment.setColor(color) lastColor = color.backgroundColor libraryViewModel.updateColor(color.backgroundColor) - ToolbarContentTintHelper.colorizeToolbar(playerToolbar, Color.WHITE, activity) + ToolbarContentTintHelper.colorizeToolbar(binding.playerToolbar, Color.WHITE, activity) - playerToolbar.setTitleTextColor(Color.WHITE) - playerToolbar.setSubtitleTextColor(Color.WHITE) + binding.playerToolbar.setTitleTextColor(Color.WHITE) + binding.playerToolbar.setSubtitleTextColor(Color.WHITE) } override fun toggleFavorite(song: Song) { @@ -86,6 +89,7 @@ class CardBlurFragment : AbsPlayerFragment(R.layout.fragment_card_blur_player), override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentCardBlurPlayerBinding.bind(view) setUpSubFragments() setUpPlayerToolbar() } @@ -99,12 +103,12 @@ class CardBlurFragment : AbsPlayerFragment(R.layout.fragment_card_blur_player), } private fun setUpPlayerToolbar() { - playerToolbar.apply { + binding.playerToolbar.apply { inflateMenu(R.menu.menu_player) setNavigationOnClickListener { requireActivity().onBackPressed() } setTitleTextColor(Color.WHITE) setSubtitleTextColor(Color.WHITE) - ToolbarContentTintHelper.colorizeToolbar(playerToolbar, Color.WHITE, activity) + ToolbarContentTintHelper.colorizeToolbar(binding.playerToolbar, Color.WHITE, activity) }.setOnMenuItemClickListener(this) } @@ -122,7 +126,7 @@ class CardBlurFragment : AbsPlayerFragment(R.layout.fragment_card_blur_player), private fun updateSong() { val song = MusicPlayerRemote.currentSong - playerToolbar.apply { + binding.playerToolbar.apply { title = song.title subtitle = song.artistName } @@ -131,19 +135,19 @@ class CardBlurFragment : AbsPlayerFragment(R.layout.fragment_card_blur_player), private fun updateBlur() { val blurAmount = PreferenceManager.getDefaultSharedPreferences(requireContext()) .getInt(NEW_BLUR_AMOUNT, 25) - colorBackground!!.clearColorFilter() - SongGlideRequest.Builder.from(Glide.with(requireActivity()), MusicPlayerRemote.currentSong) - .checkIgnoreMediaStore(requireContext()) - .generatePalette(requireContext()).build() + binding.colorBackground.clearColorFilter() + GlideApp.with(requireActivity()).asBitmapPalette() + .songCoverOptions(MusicPlayerRemote.currentSong) + .load(RetroGlideExtension.getSongModel(MusicPlayerRemote.currentSong)) .dontAnimate() .transform( BlurTransformation.Builder(requireContext()).blurRadius(blurAmount.toFloat()) .build() ) - .into(object : RetroMusicColoredTarget(colorBackground) { + .into(object : RetroMusicColoredTarget(binding.colorBackground) { override fun onColorReady(colors: MediaNotificationProcessor) { if (colors.backgroundColor == defaultFooterColor) { - colorBackground.setColorFilter(colors.backgroundColor) + binding.colorBackground.setColorFilter(colors.backgroundColor) } } }) @@ -159,6 +163,7 @@ class CardBlurFragment : AbsPlayerFragment(R.layout.fragment_card_blur_player), super.onDestroyView() PreferenceManager.getDefaultSharedPreferences(requireContext()) .unregisterOnSharedPreferenceChangeListener(this) + _binding = null } override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/cardblur/CardBlurPlaybackControlsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/cardblur/CardBlurPlaybackControlsFragment.kt index c641efa5..a78ed750 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/cardblur/CardBlurPlaybackControlsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/cardblur/CardBlurPlaybackControlsFragment.kt @@ -25,6 +25,7 @@ import android.widget.SeekBar import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentCardBlurPlayerPlaybackControlsBinding import code.name.monkey.retromusic.extensions.applyColor import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.show @@ -37,8 +38,6 @@ import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import kotlinx.android.synthetic.main.fragment_card_blur_player_playback_controls.* -import kotlinx.android.synthetic.main.media_button.* class CardBlurPlaybackControlsFragment : AbsPlayerControlsFragment(R.layout.fragment_card_blur_player_playback_controls) { @@ -47,6 +46,10 @@ class CardBlurPlaybackControlsFragment : private var lastDisabledPlaybackControlsColor: Int = 0 private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper + private var _binding: FragmentCardBlurPlayerPlaybackControlsBinding? = null + private val binding get() = _binding!! + + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) progressViewUpdateHelper = MusicProgressViewUpdateHelper(this) @@ -54,6 +57,7 @@ class CardBlurPlaybackControlsFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentCardBlurPlayerPlaybackControlsBinding.bind(view) setUpMusicControllers() } @@ -70,7 +74,7 @@ class CardBlurPlaybackControlsFragment : } private fun setUpPlayPauseFab() { - playPauseButton.apply { + binding.mediaButton.playPauseButton.apply { TintHelper.setTintAuto(this, Color.WHITE, true) TintHelper.setTintAuto(this, Color.BLACK, false) setOnClickListener(PlayPauseButtonOnClickHandler()) @@ -79,16 +83,16 @@ class CardBlurPlaybackControlsFragment : private fun updatePlayPauseDrawableState() { when { - MusicPlayerRemote.isPlaying -> playPauseButton.setImageResource(R.drawable.ic_pause) - else -> playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_32dp) + MusicPlayerRemote.isPlaying -> binding.mediaButton.playPauseButton.setImageResource(R.drawable.ic_pause) + else -> binding.mediaButton.playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_32dp) } } private fun updateProgressTextColor() { val color = Color.WHITE - songTotalTime.setTextColor(color) - songCurrentProgress.setTextColor(color) - songInfo.setTextColor(color) + binding.songTotalTime.setTextColor(color) + binding.songCurrentProgress.setTextColor(color) + binding.songInfo.setTextColor(color) } override fun onResume() { @@ -115,10 +119,10 @@ class CardBlurPlaybackControlsFragment : private fun updateSong() { if (PreferenceUtil.isSongInfo) { - songInfo.text = getSongInfo(MusicPlayerRemote.currentSong) - songInfo.show() + binding.songInfo.text = getSongInfo(MusicPlayerRemote.currentSong) + binding.songInfo.show() } else { - songInfo.hide() + binding.songInfo.hide() } } @@ -144,26 +148,32 @@ class CardBlurPlaybackControlsFragment : private fun setUpPrevNext() { updatePrevNextColor() - nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } - previousButton.setOnClickListener { MusicPlayerRemote.back() } + binding.mediaButton.nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } + binding.mediaButton.previousButton.setOnClickListener { MusicPlayerRemote.back() } } private fun updatePrevNextColor() { - nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) - previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.mediaButton.nextButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) + binding.mediaButton.previousButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } private fun setUpShuffleButton() { - shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } + binding.mediaButton.shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } } override fun updateShuffleState() { when (MusicPlayerRemote.shuffleMode) { - MusicService.SHUFFLE_MODE_SHUFFLE -> shuffleButton.setColorFilter( + MusicService.SHUFFLE_MODE_SHUFFLE -> binding.mediaButton.shuffleButton.setColorFilter( lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) - else -> shuffleButton.setColorFilter( + else -> binding.mediaButton.shuffleButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) @@ -171,31 +181,37 @@ class CardBlurPlaybackControlsFragment : } private fun setUpRepeatButton() { - repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } + binding.mediaButton.repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } } override fun updateRepeatState() { when (MusicPlayerRemote.repeatMode) { MusicService.REPEAT_MODE_NONE -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter( + binding.mediaButton.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.mediaButton.repeatButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) } MusicService.REPEAT_MODE_ALL -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.mediaButton.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.mediaButton.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } MusicService.REPEAT_MODE_THIS -> { - repeatButton.setImageResource(R.drawable.ic_repeat_one) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.mediaButton.repeatButton.setImageResource(R.drawable.ic_repeat_one) + binding.mediaButton.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } } } public override fun show() { - playPauseButton!!.animate() + binding.mediaButton.playPauseButton.animate() .scaleX(1f) .scaleY(1f) .rotation(360f) @@ -204,18 +220,16 @@ class CardBlurPlaybackControlsFragment : } public override fun hide() { - if (playPauseButton != null) { - playPauseButton!!.apply { - scaleX = 0f - scaleY = 0f - rotation = 0f - } + binding.mediaButton.playPauseButton.apply { + scaleX = 0f + scaleY = 0f + rotation = 0f } } override fun setUpProgressSlider() { - progressSlider.applyColor(Color.WHITE) - progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { + binding.progressSlider.applyColor(Color.WHITE) + binding.progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) { MusicPlayerRemote.seekTo(progress) @@ -229,14 +243,19 @@ class CardBlurPlaybackControlsFragment : } override fun onUpdateProgressViews(progress: Int, total: Int) { - progressSlider.max = total + binding.progressSlider.max = total - val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) + val animator = ObjectAnimator.ofInt(binding.progressSlider, "progress", progress) animator.duration = SLIDER_ANIMATION_TIME animator.interpolator = LinearInterpolator() animator.start() - songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) - songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) + binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/circle/CirclePlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/circle/CirclePlayerFragment.kt index 7a762af1..5701f528 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/circle/CirclePlayerFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/circle/CirclePlayerFragment.kt @@ -32,12 +32,15 @@ import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentCirclePlayerBinding import code.name.monkey.retromusic.extensions.accentColor import code.name.monkey.retromusic.extensions.applyColor import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment +import code.name.monkey.retromusic.fragments.base.goToAlbum +import code.name.monkey.retromusic.fragments.base.goToArtist import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper.Callback @@ -51,7 +54,6 @@ import code.name.monkey.retromusic.views.SeekArc import code.name.monkey.retromusic.views.SeekArc.OnSeekArcChangeListener import code.name.monkey.retromusic.volume.AudioVolumeObserver import code.name.monkey.retromusic.volume.OnAudioVolumeChangedListener -import kotlinx.android.synthetic.main.fragment_circle_player.* /** * Created by hemanths on 2020-01-06. @@ -67,6 +69,9 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player), private val audioManager: AudioManager? get() = requireContext().getSystemService(Context.AUDIO_SERVICE) as AudioManager + private var _binding: FragmentCirclePlayerBinding? = null + private val binding get() = _binding!! + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) progressViewUpdateHelper = MusicProgressViewUpdateHelper(this) @@ -76,18 +81,25 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player), inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { - return inflater.inflate(R.layout.fragment_circle_player, container, false) + ): View { + _binding = FragmentCirclePlayerBinding.inflate(inflater, container, false) + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setupViews() - title.isSelected = true + binding.title.isSelected = true + binding.title.setOnClickListener { + goToAlbum(requireActivity()) + } + binding.text.setOnClickListener { + goToArtist(requireActivity()) + } } private fun setUpPlayerToolbar() { - playerToolbar.apply { + binding.playerToolbar.apply { inflateMenu(R.menu.menu_player) setNavigationOnClickListener { requireActivity().onBackPressed() } setOnMenuItemClickListener(this@CirclePlayerFragment) @@ -102,12 +114,12 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player), private fun setupViews() { setUpProgressSlider() ViewUtil.setProgressDrawable( - progressSlider, + binding.progressSlider, ThemeStore.accentColor(requireContext()), false ) - volumeSeekBar.progressColor = accentColor() - volumeSeekBar.arcColor = ColorUtil.withAlpha(accentColor(), 0.25f) + binding.volumeSeekBar.progressColor = accentColor() + binding.volumeSeekBar.arcColor = ColorUtil.withAlpha(accentColor(), 0.25f) setUpPlayPauseFab() setUpPrevNext() setUpPlayerToolbar() @@ -115,19 +127,23 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player), private fun setUpPrevNext() { updatePrevNextColor() - nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } - previousButton.setOnClickListener { MusicPlayerRemote.back() } + binding.nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } + binding.previousButton.setOnClickListener { MusicPlayerRemote.back() } } private fun updatePrevNextColor() { val accentColor = ThemeStore.accentColor(requireContext()) - nextButton.setColorFilter(accentColor, PorterDuff.Mode.SRC_IN) - previousButton.setColorFilter(accentColor, PorterDuff.Mode.SRC_IN) + binding.nextButton.setColorFilter(accentColor, PorterDuff.Mode.SRC_IN) + binding.previousButton.setColorFilter(accentColor, PorterDuff.Mode.SRC_IN) } private fun setUpPlayPauseFab() { - TintHelper.setTintAuto(playPauseButton, ThemeStore.accentColor(requireContext()), false) - playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) + TintHelper.setTintAuto( + binding.playPauseButton, + ThemeStore.accentColor(requireContext()), + false + ) + binding.playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) } override fun onResume() { @@ -140,10 +156,10 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player), val audioManager = audioManager if (audioManager != null) { - volumeSeekBar.max = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) - volumeSeekBar.progress = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) + binding.volumeSeekBar.max = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) + binding.volumeSeekBar.progress = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) } - volumeSeekBar.setOnSeekArcChangeListener(this) + binding.volumeSeekBar.setOnSeekArcChangeListener(this) } override fun onPause() { @@ -151,8 +167,8 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player), progressViewUpdateHelper.stop() } - override fun playerToolbar(): Toolbar? { - return playerToolbar + override fun playerToolbar(): Toolbar { + return binding.playerToolbar } override fun onShow() { @@ -192,30 +208,27 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player), private fun updateSong() { val song = MusicPlayerRemote.currentSong - title.text = song.title - text.text = song.artistName + binding.title.text = song.title + binding.text.text = song.artistName if (PreferenceUtil.isSongInfo) { - songInfo.text = getSongInfo(song) - songInfo.show() + binding.songInfo.text = getSongInfo(song) + binding.songInfo.show() } else { - songInfo.hide() + binding.songInfo.hide() } } private fun updatePlayPauseDrawableState() { when { - MusicPlayerRemote.isPlaying -> playPauseButton.setImageResource(R.drawable.ic_pause) - else -> playPauseButton.setImageResource(R.drawable.ic_play_arrow) + MusicPlayerRemote.isPlaying -> binding.playPauseButton.setImageResource(R.drawable.ic_pause) + else -> binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow) } } override fun onAudioVolumeChanged(currentVolume: Int, maxVolume: Int) { - if (volumeSeekBar == null) { - return - } - volumeSeekBar.max = maxVolume - volumeSeekBar.progress = currentVolume + _binding?.volumeSeekBar?.max = maxVolume + _binding?.volumeSeekBar?.progress = currentVolume } override fun onDestroyView() { @@ -223,6 +236,7 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player), if (audioVolumeObserver != null) { audioVolumeObserver!!.unregister() } + _binding = null } override fun onProgressChanged(seekArc: SeekArc?, progress: Int, fromUser: Boolean) { @@ -237,8 +251,8 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player), } fun setUpProgressSlider() { - progressSlider.applyColor(accentColor()) - progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { + binding.progressSlider.applyColor(accentColor()) + binding.progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) { MusicPlayerRemote.seekTo(progress) @@ -252,14 +266,14 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player), } override fun onUpdateProgressViews(progress: Int, total: Int) { - progressSlider.max = total + binding.progressSlider.max = total - val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) + val animator = ObjectAnimator.ofInt(binding.progressSlider, "progress", progress) animator.duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME animator.interpolator = LinearInterpolator() animator.start() - songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) - songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) + binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/classic/ClassicPlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/classic/ClassicPlayerFragment.kt index 2e1ac883..34f30bd2 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/classic/ClassicPlayerFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/classic/ClassicPlayerFragment.kt @@ -35,11 +35,14 @@ import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.retromusic.R import code.name.monkey.retromusic.RetroBottomSheetBehavior import code.name.monkey.retromusic.adapter.song.PlayingQueueAdapter +import code.name.monkey.retromusic.databinding.FragmentClassicPlayerBinding import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.fragments.VolumeFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment +import code.name.monkey.retromusic.fragments.base.goToAlbum +import code.name.monkey.retromusic.fragments.base.goToArtist import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper @@ -60,13 +63,14 @@ import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropM import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils -import kotlinx.android.synthetic.main.fragment_classic_controls.* -import kotlinx.android.synthetic.main.fragment_classic_player.* class ClassicPlayerFragment : AbsPlayerFragment(R.layout.fragment_classic_player), View.OnLayoutChangeListener, MusicProgressViewUpdateHelper.Callback { + private var _binding: FragmentClassicPlayerBinding? = null + private val binding get() = _binding!! + private var lastColor: Int = 0 private var lastPlaybackControlsColor: Int = 0 private var lastDisabledPlaybackControlsColor: Int = 0 @@ -83,11 +87,11 @@ class ClassicPlayerFragment : AbsPlayerFragment(R.layout.fragment_classic_player private val bottomSheetCallbackList = object : BottomSheetBehavior.BottomSheetCallback() { override fun onSlide(bottomSheet: View, slideOffset: Float) { mainActivity.getBottomSheetBehavior().setAllowDragging(false) - playerQueueSheet.setContentPadding( - playerQueueSheet.contentPaddingLeft, - (slideOffset * status_bar.height).toInt(), - playerQueueSheet.contentPaddingRight, - playerQueueSheet.contentPaddingBottom + binding.playerQueueSheet.setContentPadding( + binding.playerQueueSheet.contentPaddingLeft, + (slideOffset * binding.statusBar.height).toInt(), + binding.playerQueueSheet.contentPaddingRight, + binding.playerQueueSheet.contentPaddingBottom ) shapeDrawable.interpolation = 1 - slideOffset @@ -118,6 +122,7 @@ class ClassicPlayerFragment : AbsPlayerFragment(R.layout.fragment_classic_player @SuppressLint("ClickableViewAccessibility") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentClassicPlayerBinding.bind(view) setupPanel() setUpMusicControllers() setUpPlayerToolbar() @@ -139,19 +144,25 @@ class ClassicPlayerFragment : AbsPlayerFragment(R.layout.fragment_classic_player ) shapeDrawable.fillColor = ColorStateList.valueOf(ATHUtil.resolveColor(requireContext(), R.attr.colorSurface)) - playerQueueSheet.background = shapeDrawable + binding.playerQueueSheet.background = shapeDrawable - playerQueueSheet.setOnTouchListener { _, _ -> + binding.playerQueueSheet.setOnTouchListener { _, _ -> mainActivity.getBottomSheetBehavior().setAllowDragging(false) getQueuePanel().setAllowDragging(true) return@setOnTouchListener false } ToolbarContentTintHelper.colorizeToolbar( - playerToolbar, + binding.playerToolbar, Color.WHITE, requireActivity() ) + binding.title.setOnClickListener { + goToAlbum(requireActivity()) + } + binding.text.setOnClickListener { + goToArtist(requireActivity()) + } } private fun hideVolumeIfAvailable() { @@ -179,18 +190,19 @@ class ClassicPlayerFragment : AbsPlayerFragment(R.layout.fragment_classic_player } WrapperAdapterUtils.releaseAll(wrappedAdapter) + _binding = null } private fun updateSong() { val song = MusicPlayerRemote.currentSong - title.text = song.title - text.text = song.artistName + binding.title.text = song.title + binding.text.text = song.artistName if (PreferenceUtil.isSongInfo) { - songInfo.text = getSongInfo(song) - songInfo.show() + binding.playerControlsContainer.songInfo.text = getSongInfo(song) + binding.playerControlsContainer.songInfo.show() } else { - songInfo.hide() + binding.playerControlsContainer.songInfo.hide() } } @@ -236,7 +248,7 @@ class ClassicPlayerFragment : AbsPlayerFragment(R.layout.fragment_classic_player } override fun playerToolbar(): Toolbar? { - return playerToolbar + return binding.playerToolbar } override fun onShow() { @@ -269,25 +281,37 @@ class ClassicPlayerFragment : AbsPlayerFragment(R.layout.fragment_classic_player lastPlaybackControlsColor = color.primaryTextColor lastDisabledPlaybackControlsColor = ColorUtil.withAlpha(color.primaryTextColor, 0.3f) - playerContainer.setBackgroundColor(color.backgroundColor) - songInfo.setTextColor(color.primaryTextColor) - player_queue_sub_header.setTextColor(color.primaryTextColor) + binding.playerContainer.setBackgroundColor(color.backgroundColor) + binding.playerControlsContainer.songInfo.setTextColor(color.primaryTextColor) + binding.playerQueueSubHeader.setTextColor(color.primaryTextColor) - songCurrentProgress.setTextColor(lastPlaybackControlsColor) - songTotalTime.setTextColor(lastPlaybackControlsColor) + binding.playerControlsContainer.songCurrentProgress.setTextColor(lastPlaybackControlsColor) + binding.playerControlsContainer.songTotalTime.setTextColor(lastPlaybackControlsColor) - ViewUtil.setProgressDrawable(progressSlider, color.primaryTextColor, true) + ViewUtil.setProgressDrawable( + binding.playerControlsContainer.progressSlider, + color.primaryTextColor, + true + ) volumeFragment?.setTintableColor(color.primaryTextColor) - TintHelper.setTintAuto(playPauseButton, color.primaryTextColor, true) - TintHelper.setTintAuto(playPauseButton, color.backgroundColor, false) + TintHelper.setTintAuto( + binding.playerControlsContainer.playPauseButton, + color.primaryTextColor, + true + ) + TintHelper.setTintAuto( + binding.playerControlsContainer.playPauseButton, + color.backgroundColor, + false + ) updateRepeatState() updateShuffleState() updatePrevNextColor() ToolbarContentTintHelper.colorizeToolbar( - playerToolbar, + binding.playerToolbar, Color.WHITE, requireActivity() ) @@ -305,15 +329,21 @@ class ClassicPlayerFragment : AbsPlayerFragment(R.layout.fragment_classic_player } override fun onUpdateProgressViews(progress: Int, total: Int) { - progressSlider.max = total + binding.playerControlsContainer.progressSlider.max = total - val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) + val animator = ObjectAnimator.ofInt( + binding.playerControlsContainer.progressSlider, + "progress", + progress + ) animator.duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME animator.interpolator = LinearInterpolator() animator.start() - songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) - songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + binding.playerControlsContainer.songTotalTime.text = + MusicUtil.getReadableDurationString(total.toLong()) + binding.playerControlsContainer.songCurrentProgress.text = + MusicUtil.getReadableDurationString(progress.toLong()) } private fun updateQueuePosition() { @@ -327,33 +357,33 @@ class ClassicPlayerFragment : AbsPlayerFragment(R.layout.fragment_classic_player } private fun resetToCurrentPosition() { - recyclerView.stopScroll() + binding.recyclerView.stopScroll() linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0) } private fun getQueuePanel(): RetroBottomSheetBehavior { - return RetroBottomSheetBehavior.from(playerQueueSheet) as RetroBottomSheetBehavior + return RetroBottomSheetBehavior.from(binding.playerQueueSheet) as RetroBottomSheetBehavior } private fun setupPanel() { - if (!ViewCompat.isLaidOut(playerContainer) || playerContainer.isLayoutRequested) { - playerContainer.addOnLayoutChangeListener(this) + if (!ViewCompat.isLaidOut(binding.playerContainer) || binding.playerContainer.isLayoutRequested) { + binding.playerContainer.addOnLayoutChangeListener(this) return } - val height = playerContainer.height - val width = playerContainer.width + val height = binding.playerContainer.height + val width = binding.playerContainer.width val finalHeight = height - width val panel = getQueuePanel() panel.peekHeight = finalHeight } private fun setUpPlayerToolbar() { - playerToolbar.inflateMenu(R.menu.menu_player) - playerToolbar.setNavigationOnClickListener { requireActivity().onBackPressed() } - playerToolbar.setOnMenuItemClickListener(this) + binding.playerToolbar.inflateMenu(R.menu.menu_player) + binding.playerToolbar.setNavigationOnClickListener { requireActivity().onBackPressed() } + binding.playerToolbar.setOnMenuItemClickListener(this) ToolbarContentTintHelper.colorizeToolbar( - playerToolbar, + binding.playerToolbar, Color.WHITE, requireActivity() ) @@ -377,18 +407,19 @@ class ClassicPlayerFragment : AbsPlayerFragment(R.layout.fragment_classic_player recyclerViewDragDropManager?.createWrappedAdapter(playingQueueAdapter!!) as RecyclerView.Adapter<*> wrappedAdapter = recyclerViewSwipeManager?.createWrappedAdapter(wrappedAdapter) as RecyclerView.Adapter<*> - recyclerView.layoutManager = linearLayoutManager - recyclerView.adapter = wrappedAdapter - recyclerView.itemAnimator = animator - recyclerViewTouchActionGuardManager?.attachRecyclerView(recyclerView) - recyclerViewDragDropManager?.attachRecyclerView(recyclerView) - recyclerViewSwipeManager?.attachRecyclerView(recyclerView) + binding.recyclerView.layoutManager = linearLayoutManager + binding.recyclerView.adapter = wrappedAdapter + binding.recyclerView.itemAnimator = animator + recyclerViewTouchActionGuardManager?.attachRecyclerView(binding.recyclerView) + recyclerViewDragDropManager?.attachRecyclerView(binding.recyclerView) + recyclerViewSwipeManager?.attachRecyclerView(binding.recyclerView) linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0) } fun setUpProgressSlider() { - progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { + binding.playerControlsContainer.progressSlider.setOnSeekBarChangeListener(object : + SimpleOnSeekbarChangeListener() { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) { MusicPlayerRemote.seekTo(progress) @@ -402,14 +433,16 @@ class ClassicPlayerFragment : AbsPlayerFragment(R.layout.fragment_classic_player } private fun setUpPlayPauseFab() { - playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) + binding.playerControlsContainer.playPauseButton.setOnClickListener( + PlayPauseButtonOnClickHandler() + ) } private fun updatePlayPauseDrawableState() { if (MusicPlayerRemote.isPlaying) { - playPauseButton.setImageResource(R.drawable.ic_pause) + binding.playerControlsContainer.playPauseButton.setImageResource(R.drawable.ic_pause) } else { - playPauseButton.setImageResource(R.drawable.ic_play_arrow) + binding.playerControlsContainer.playPauseButton.setImageResource(R.drawable.ic_play_arrow) } } @@ -423,27 +456,33 @@ class ClassicPlayerFragment : AbsPlayerFragment(R.layout.fragment_classic_player private fun setUpPrevNext() { updatePrevNextColor() - nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } - previousButton.setOnClickListener { MusicPlayerRemote.back() } + binding.playerControlsContainer.nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } + binding.playerControlsContainer.previousButton.setOnClickListener { MusicPlayerRemote.back() } } private fun updatePrevNextColor() { - nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) - previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.playerControlsContainer.nextButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) + binding.playerControlsContainer.previousButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } private fun setUpShuffleButton() { - shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } + binding.playerControlsContainer.shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } } fun updateShuffleState() { when (MusicPlayerRemote.shuffleMode) { MusicService.SHUFFLE_MODE_SHUFFLE -> - shuffleButton.setColorFilter( + binding.playerControlsContainer.shuffleButton.setColorFilter( lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) - else -> shuffleButton.setColorFilter( + else -> binding.playerControlsContainer.shuffleButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) @@ -451,25 +490,31 @@ class ClassicPlayerFragment : AbsPlayerFragment(R.layout.fragment_classic_player } private fun setUpRepeatButton() { - repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } + binding.playerControlsContainer.repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } } fun updateRepeatState() { when (MusicPlayerRemote.repeatMode) { MusicService.REPEAT_MODE_NONE -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter( + binding.playerControlsContainer.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.playerControlsContainer.repeatButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) } MusicService.REPEAT_MODE_ALL -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.playerControlsContainer.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.playerControlsContainer.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } MusicService.REPEAT_MODE_THIS -> { - repeatButton.setImageResource(R.drawable.ic_repeat_one) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.playerControlsContainer.repeatButton.setImageResource(R.drawable.ic_repeat_one) + binding.playerControlsContainer.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } } } @@ -485,9 +530,9 @@ class ClassicPlayerFragment : AbsPlayerFragment(R.layout.fragment_classic_player oldRight: Int, oldBottom: Int ) { - val height = playerContainer.height - val width = playerContainer.width - val finalHeight = height - (playerControlsContainer.height + width) + val height = binding.playerContainer.height + val width = binding.playerContainer.width + val finalHeight = height - (binding.playerControlsContainer.root.height + width) val panel = getQueuePanel() panel.peekHeight = finalHeight } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/color/ColorFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/color/ColorFragment.kt index 87dffad5..461afbcf 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/color/ColorFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/color/ColorFragment.kt @@ -19,16 +19,17 @@ import android.os.Bundle import android.os.Handler import android.view.View import androidx.appcompat.widget.Toolbar +import androidx.core.animation.doOnEnd import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentColorPlayerBinding import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import kotlinx.android.synthetic.main.fragment_color_player.* class ColorFragment : AbsPlayerFragment(R.layout.fragment_color_player) { @@ -36,9 +37,12 @@ class ColorFragment : AbsPlayerFragment(R.layout.fragment_color_player) { private var navigationColor: Int = 0 private lateinit var playbackControlsFragment: ColorPlaybackControlsFragment private var valueAnimator: ValueAnimator? = null + private var _binding: FragmentColorPlayerBinding? = null + private val binding get() = _binding!! + override fun playerToolbar(): Toolbar { - return playerToolbar + return binding.playerToolbar } override val paletteColor: Int @@ -50,11 +54,17 @@ class ColorFragment : AbsPlayerFragment(R.layout.fragment_color_player) { playbackControlsFragment.setColor(color) navigationColor = color.backgroundColor - colorGradientBackground?.setBackgroundColor(color.backgroundColor) + binding.colorGradientBackground.setBackgroundColor(color.backgroundColor) + val animator = + playbackControlsFragment.createRevealAnimator(binding.colorGradientBackground) + animator.doOnEnd { + _binding?.root?.setBackgroundColor(color.backgroundColor) + } + animator.start() serviceActivity?.setLightNavigationBar(ColorUtil.isColorLight(color.backgroundColor)) Handler().post { ToolbarContentTintHelper.colorizeToolbar( - playerToolbar, + binding.playerToolbar, color.secondaryTextColor, requireActivity() ) @@ -95,10 +105,12 @@ class ColorFragment : AbsPlayerFragment(R.layout.fragment_color_player) { valueAnimator!!.cancel() valueAnimator = null } + _binding = null } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentColorPlayerBinding.bind(view) setUpSubFragments() setUpPlayerToolbar() val playerAlbumCoverFragment = @@ -112,7 +124,7 @@ class ColorFragment : AbsPlayerFragment(R.layout.fragment_color_player) { } private fun setUpPlayerToolbar() { - playerToolbar.apply { + binding.playerToolbar.apply { inflateMenu(R.menu.menu_player) setNavigationOnClickListener { requireActivity().onBackPressed() } setOnMenuItemClickListener(this@ColorFragment) diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/color/ColorPlaybackControlsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/color/ColorPlaybackControlsFragment.kt index 3cd0ecb9..7b3170af 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/color/ColorPlaybackControlsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/color/ColorPlaybackControlsFragment.kt @@ -14,23 +14,29 @@ */ package code.name.monkey.retromusic.fragments.player.color +import android.animation.Animator import android.animation.ObjectAnimator import android.graphics.Color import android.graphics.PorterDuff import android.os.Bundle import android.view.LayoutInflater import android.view.View +import android.view.ViewAnimationUtils import android.view.ViewGroup +import android.view.animation.AccelerateInterpolator import android.view.animation.DecelerateInterpolator import android.view.animation.LinearInterpolator import android.widget.SeekBar import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentColorPlayerPlaybackControlsBinding import code.name.monkey.retromusic.extensions.applyColor import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment +import code.name.monkey.retromusic.fragments.base.goToAlbum +import code.name.monkey.retromusic.fragments.base.goToArtist import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler @@ -39,7 +45,7 @@ import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import kotlinx.android.synthetic.main.fragment_color_player_playback_controls.* +import kotlin.math.sqrt class ColorPlaybackControlsFragment : AbsPlayerControlsFragment(R.layout.fragment_adaptive_player_playback_controls) { @@ -47,6 +53,9 @@ class ColorPlaybackControlsFragment : private var lastPlaybackControlsColor: Int = 0 private var lastDisabledPlaybackControlsColor: Int = 0 private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper + private var _binding: FragmentColorPlayerPlaybackControlsBinding? = null + private val binding get() = _binding!! + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -57,8 +66,9 @@ class ColorPlaybackControlsFragment : inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { - return inflater.inflate(R.layout.fragment_color_player_playback_controls, container, false) + ): View { + _binding = FragmentColorPlayerPlaybackControlsBinding.inflate(inflater, container, false) + return binding.root } override fun onResume() { @@ -74,20 +84,26 @@ class ColorPlaybackControlsFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setUpMusicControllers() - title.isSelected = true - text.isSelected = true + binding.title.isSelected = true + binding.text.isSelected = true + binding.title.setOnClickListener { + goToAlbum(requireActivity()) + } + binding.text.setOnClickListener { + goToArtist(requireActivity()) + } } private fun updateSong() { val song = MusicPlayerRemote.currentSong - title.text = song.title - text.text = song.artistName + binding.title.text = song.title + binding.text.text = song.artistName if (PreferenceUtil.isSongInfo) { - songInfo.text = getSongInfo(song) - songInfo.show() + binding.songInfo.text = getSongInfo(song) + binding.songInfo.show() } else { - songInfo.hide() + binding.songInfo.hide() } } @@ -116,15 +132,15 @@ class ColorPlaybackControlsFragment : } override fun setColor(color: MediaNotificationProcessor) { - TintHelper.setTintAuto(playPauseButton, color.primaryTextColor, true) - TintHelper.setTintAuto(playPauseButton, color.backgroundColor, false) - progressSlider.applyColor(color.primaryTextColor) + TintHelper.setTintAuto(binding.playPauseButton, color.primaryTextColor, true) + TintHelper.setTintAuto(binding.playPauseButton, color.backgroundColor, false) + binding.progressSlider.applyColor(color.primaryTextColor) - title.setTextColor(color.primaryTextColor) - text.setTextColor(color.secondaryTextColor) - songInfo.setTextColor(color.secondaryTextColor) - songCurrentProgress.setTextColor(color.secondaryTextColor) - songTotalTime.setTextColor(color.secondaryTextColor) + binding.title.setTextColor(color.primaryTextColor) + binding.text.setTextColor(color.secondaryTextColor) + binding.songInfo.setTextColor(color.secondaryTextColor) + binding.songCurrentProgress.setTextColor(color.secondaryTextColor) + binding.songTotalTime.setTextColor(color.secondaryTextColor) volumeFragment?.setTintableColor(color.primaryTextColor) lastPlaybackControlsColor = color.secondaryTextColor @@ -136,15 +152,15 @@ class ColorPlaybackControlsFragment : } private fun setUpPlayPauseFab() { - TintHelper.setTintAuto(playPauseButton, Color.WHITE, true) - TintHelper.setTintAuto(playPauseButton, Color.BLACK, false) - playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) + TintHelper.setTintAuto(binding.playPauseButton, Color.WHITE, true) + TintHelper.setTintAuto(binding.playPauseButton, Color.BLACK, false) + binding.playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) } private fun updatePlayPauseDrawableState() { when { - MusicPlayerRemote.isPlaying -> playPauseButton.setImageResource(R.drawable.ic_pause) - else -> playPauseButton.setImageResource(R.drawable.ic_play_arrow) + MusicPlayerRemote.isPlaying -> binding.playPauseButton.setImageResource(R.drawable.ic_pause) + else -> binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow) } } @@ -158,26 +174,26 @@ class ColorPlaybackControlsFragment : private fun setUpPrevNext() { updatePrevNextColor() - nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } - previousButton.setOnClickListener { MusicPlayerRemote.back() } + binding.nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } + binding.previousButton.setOnClickListener { MusicPlayerRemote.back() } } private fun updatePrevNextColor() { - nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) - previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) } private fun setUpShuffleButton() { - shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } + binding.shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } } override fun updateShuffleState() { when (MusicPlayerRemote.shuffleMode) { - MusicService.SHUFFLE_MODE_SHUFFLE -> shuffleButton.setColorFilter( + MusicService.SHUFFLE_MODE_SHUFFLE -> binding.shuffleButton.setColorFilter( lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) - else -> shuffleButton.setColorFilter( + else -> binding.shuffleButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) @@ -185,31 +201,37 @@ class ColorPlaybackControlsFragment : } private fun setUpRepeatButton() { - repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } + binding.repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } } override fun updateRepeatState() { when (MusicPlayerRemote.repeatMode) { MusicService.REPEAT_MODE_NONE -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter( + binding.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.repeatButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) } MusicService.REPEAT_MODE_ALL -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } MusicService.REPEAT_MODE_THIS -> { - repeatButton.setImageResource(R.drawable.ic_repeat_one) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat_one) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } } } public override fun show() { - playPauseButton!!.animate() + binding.playPauseButton.animate() .scaleX(1f) .scaleY(1f) .rotation(360f) @@ -218,7 +240,7 @@ class ColorPlaybackControlsFragment : } public override fun hide() { - playPauseButton.apply { + binding.playPauseButton.apply { scaleX = 0f scaleY = 0f rotation = 0f @@ -226,7 +248,7 @@ class ColorPlaybackControlsFragment : } override fun setUpProgressSlider() { - progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { + binding.progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) { MusicPlayerRemote.seekTo(progress) @@ -240,14 +262,37 @@ class ColorPlaybackControlsFragment : } override fun onUpdateProgressViews(progress: Int, total: Int) { - progressSlider.max = total + binding.progressSlider.max = total - val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) + val animator = ObjectAnimator.ofInt(binding.progressSlider, "progress", progress) animator.duration = SLIDER_ANIMATION_TIME animator.interpolator = LinearInterpolator() animator.start() - songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) - songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) + binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + } + + fun createRevealAnimator(view: View): Animator { + val location = IntArray(2) + binding.playPauseButton.getLocationOnScreen(location) + val x = (location[0] + binding.playPauseButton.measuredWidth / 2) + val y = (location[1] + binding.playPauseButton.measuredHeight / 2) + val endRadius = sqrt((x * x + y * y).toFloat()) + val startRadius = + binding.playPauseButton.measuredWidth.coerceAtMost(binding.playPauseButton.measuredHeight) + return ViewAnimationUtils.createCircularReveal( + view, x, y, startRadius.toFloat(), + endRadius + ).apply { + duration = 300 + interpolator = AccelerateInterpolator() + } + + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/fit/FitFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/fit/FitFragment.kt index d036dd42..936e380e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/fit/FitFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/fit/FitFragment.kt @@ -20,16 +20,19 @@ import androidx.appcompat.widget.Toolbar import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentFitBinding import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import kotlinx.android.synthetic.main.fragment_fit.* class FitFragment : AbsPlayerFragment(R.layout.fragment_fit) { + private var _binding: FragmentFitBinding? = null + private val binding get() = _binding!! + override fun playerToolbar(): Toolbar { - return playerToolbar + return binding.playerToolbar } private var lastColor: Int = 0 @@ -60,7 +63,7 @@ class FitFragment : AbsPlayerFragment(R.layout.fragment_fit) { lastColor = color.primaryTextColor libraryViewModel.updateColor(color.primaryTextColor) ToolbarContentTintHelper.colorizeToolbar( - playerToolbar, + binding.playerToolbar, ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal), requireActivity() ) @@ -79,6 +82,7 @@ class FitFragment : AbsPlayerFragment(R.layout.fragment_fit) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentFitBinding.bind(view) setUpSubFragments() setUpPlayerToolbar() } @@ -92,7 +96,7 @@ class FitFragment : AbsPlayerFragment(R.layout.fragment_fit) { } private fun setUpPlayerToolbar() { - playerToolbar.apply { + binding.playerToolbar.apply { inflateMenu(R.menu.menu_player) setNavigationOnClickListener { requireActivity().onBackPressed() } setOnMenuItemClickListener(this@FitFragment) @@ -112,6 +116,11 @@ class FitFragment : AbsPlayerFragment(R.layout.fragment_fit) { updateIsFavorite() } + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + companion object { fun newInstance(): FitFragment { return FitFragment() diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/fit/FitPlaybackControlsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/fit/FitPlaybackControlsFragment.kt index dc76a12f..e7bab207 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/fit/FitPlaybackControlsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/fit/FitPlaybackControlsFragment.kt @@ -28,10 +28,13 @@ import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentFitPlaybackControlsBinding import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.ripAlpha import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment +import code.name.monkey.retromusic.fragments.base.goToAlbum +import code.name.monkey.retromusic.fragments.base.goToArtist import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler @@ -40,10 +43,12 @@ import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import kotlinx.android.synthetic.main.fragment_fit_playback_controls.* class FitPlaybackControlsFragment : AbsPlayerControlsFragment(R.layout.fragment_fit_playback_controls) { + private var _binding: FragmentFitPlaybackControlsBinding? = null + private val binding get() = _binding!! + private var lastPlaybackControlsColor: Int = 0 private var lastDisabledPlaybackControlsColor: Int = 0 @@ -56,30 +61,38 @@ class FitPlaybackControlsFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentFitPlaybackControlsBinding.bind(view) setUpMusicControllers() - title.isSelected = true - text.isSelected = true + binding.title.isSelected = true + binding.text.isSelected = true - playPauseButton.setOnClickListener { + binding.title.setOnClickListener { + goToAlbum(requireActivity()) + } + binding.text.setOnClickListener { + goToArtist(requireActivity()) + } + + binding.playPauseButton.setOnClickListener { if (MusicPlayerRemote.isPlaying) { MusicPlayerRemote.pauseSong() } else { MusicPlayerRemote.resumePlaying() } - showBonceAnimation() + showBounceAnimation() } } private fun updateSong() { val song = MusicPlayerRemote.currentSong - title.text = song.title - text.text = song.artistName + binding.title.text = song.title + binding.text.text = song.artistName if (PreferenceUtil.isSongInfo) { - songInfo.text = getSongInfo(song) - songInfo.show() + binding.songInfo.text = getSongInfo(song) + binding.songInfo.show() } else { - songInfo.hide() + binding.songInfo.hide() } } @@ -146,22 +159,22 @@ class FitPlaybackControlsFragment : private fun setFabColor(i: Int) { TintHelper.setTintAuto( - playPauseButton, + binding.playPauseButton, MaterialValueHelper.getPrimaryTextColor(context, ColorUtil.isColorLight(i)), false ) - TintHelper.setTintAuto(playPauseButton, i, true) + TintHelper.setTintAuto(binding.playPauseButton, i, true) } private fun setUpPlayPauseFab() { - playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) + binding.playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) } private fun updatePlayPauseDrawableState() { if (MusicPlayerRemote.isPlaying) { - playPauseButton.setImageResource(R.drawable.ic_pause) + binding.playPauseButton.setImageResource(R.drawable.ic_pause) } else { - playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_32dp) + binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_32dp) } } @@ -175,26 +188,26 @@ class FitPlaybackControlsFragment : private fun setUpPrevNext() { updatePrevNextColor() - nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } - previousButton.setOnClickListener { MusicPlayerRemote.back() } + binding.nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } + binding.previousButton.setOnClickListener { MusicPlayerRemote.back() } } private fun updatePrevNextColor() { - nextButton!!.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) - previousButton!!.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) } private fun setUpShuffleButton() { - shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } + binding.shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } } override fun updateShuffleState() { when (MusicPlayerRemote.shuffleMode) { - MusicService.SHUFFLE_MODE_SHUFFLE -> shuffleButton.setColorFilter( + MusicService.SHUFFLE_MODE_SHUFFLE -> binding.shuffleButton.setColorFilter( lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) - else -> shuffleButton.setColorFilter( + else -> binding.shuffleButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) @@ -202,31 +215,37 @@ class FitPlaybackControlsFragment : } private fun setUpRepeatButton() { - repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } + binding.repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } } override fun updateRepeatState() { when (MusicPlayerRemote.repeatMode) { MusicService.REPEAT_MODE_NONE -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter( + binding.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.repeatButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) } MusicService.REPEAT_MODE_ALL -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } MusicService.REPEAT_MODE_THIS -> { - repeatButton.setImageResource(R.drawable.ic_repeat_one) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat_one) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } } } public override fun show() { - playPauseButton!!.animate() + binding.playPauseButton.animate() .scaleX(1f) .scaleY(1f) .rotation(360f) @@ -235,17 +254,15 @@ class FitPlaybackControlsFragment : } public override fun hide() { - if (playPauseButton != null) { - playPauseButton!!.apply { - scaleX = 0f - scaleY = 0f - rotation = 0f - } + binding.playPauseButton.apply { + scaleX = 0f + scaleY = 0f + rotation = 0f } } - private fun showBonceAnimation() { - playPauseButton.apply { + private fun showBounceAnimation() { + binding.playPauseButton.apply { clearAnimation() scaleX = 0.9f scaleY = 0.9f @@ -268,7 +285,7 @@ class FitPlaybackControlsFragment : } override fun setUpProgressSlider() { - progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { + binding.progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) { MusicPlayerRemote.seekTo(progress) @@ -282,14 +299,19 @@ class FitPlaybackControlsFragment : } override fun onUpdateProgressViews(progress: Int, total: Int) { - progressSlider.max = total + binding.progressSlider.max = total - val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) + val animator = ObjectAnimator.ofInt(binding.progressSlider, "progress", progress) animator.duration = SLIDER_ANIMATION_TIME animator.interpolator = LinearInterpolator() animator.start() - songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) - songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) + binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/flat/FlatPlaybackControlsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/flat/FlatPlaybackControlsFragment.kt index ac10555b..4d277fa2 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/flat/FlatPlaybackControlsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/flat/FlatPlaybackControlsFragment.kt @@ -27,11 +27,14 @@ import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentFlatPlayerPlaybackControlsBinding import code.name.monkey.retromusic.extensions.applyColor import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.ripAlpha import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment +import code.name.monkey.retromusic.fragments.base.goToAlbum +import code.name.monkey.retromusic.fragments.base.goToArtist import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper.Callback @@ -41,7 +44,6 @@ import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import kotlinx.android.synthetic.main.fragment_flat_player_playback_controls.* class FlatPlaybackControlsFragment : AbsPlayerControlsFragment(R.layout.fragment_flat_player_playback_controls), Callback { @@ -49,6 +51,9 @@ class FlatPlaybackControlsFragment : private var lastPlaybackControlsColor: Int = 0 private var lastDisabledPlaybackControlsColor: Int = 0 private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper + private var _binding: FragmentFlatPlayerPlaybackControlsBinding? = null + private val binding get() = _binding!! + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -57,9 +62,16 @@ class FlatPlaybackControlsFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentFlatPlayerPlaybackControlsBinding.bind(view) setUpMusicControllers() - title.isSelected = true - text.isSelected = true + binding.title.isSelected = true + binding.text.isSelected = true + binding.title.setOnClickListener { + goToAlbum(requireActivity()) + } + binding.text.setOnClickListener { + goToArtist(requireActivity()) + } } override fun onResume() { @@ -73,7 +85,7 @@ class FlatPlaybackControlsFragment : } public override fun show() { - playPauseButton!!.animate() + binding.playPauseButton.animate() .scaleX(1f) .scaleY(1f) .setInterpolator(DecelerateInterpolator()) @@ -81,7 +93,7 @@ class FlatPlaybackControlsFragment : } public override fun hide() { - playPauseButton!!.apply { + binding.playPauseButton.apply { scaleX = 0f scaleY = 0f rotation = 0f @@ -109,7 +121,7 @@ class FlatPlaybackControlsFragment : updateTextColors(colorFinal) volumeFragment?.setTintable(colorFinal) - progressSlider.applyColor(colorFinal) + binding.progressSlider.applyColor(colorFinal) updateRepeatState() updateShuffleState() } @@ -121,15 +133,15 @@ class FlatPlaybackControlsFragment : val colorSecondary = MaterialValueHelper.getSecondaryTextColor(context, ColorUtil.isColorLight(darkColor)) - TintHelper.setTintAuto(playPauseButton, colorPrimary, false) - TintHelper.setTintAuto(playPauseButton, color, true) + TintHelper.setTintAuto(binding.playPauseButton, colorPrimary, false) + TintHelper.setTintAuto(binding.playPauseButton, color, true) - title.setBackgroundColor(color) - title.setTextColor(colorPrimary) - text.setBackgroundColor(darkColor) - text.setTextColor(colorSecondary) - songInfo.setBackgroundColor(darkColor) - songInfo.setTextColor(colorSecondary) + binding.title.setBackgroundColor(color) + binding.title.setTextColor(colorPrimary) + binding.text.setBackgroundColor(darkColor) + binding.text.setTextColor(colorSecondary) + binding.songInfo.setBackgroundColor(darkColor) + binding.songInfo.setTextColor(colorSecondary) } override fun onServiceConnected() { @@ -149,14 +161,14 @@ class FlatPlaybackControlsFragment : } private fun setUpPlayPauseFab() { - playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) + binding.playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) } private fun updatePlayPauseDrawableState() { if (MusicPlayerRemote.isPlaying) { - playPauseButton.setImageResource(R.drawable.ic_pause) + binding.playPauseButton.setImageResource(R.drawable.ic_pause) } else { - playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_32dp) + binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_32dp) } } @@ -169,13 +181,13 @@ class FlatPlaybackControlsFragment : private fun updateSong() { val song = MusicPlayerRemote.currentSong - title.text = song.title - text.text = song.artistName + binding.title.text = song.title + binding.text.text = song.artistName if (PreferenceUtil.isSongInfo) { - songInfo.text = getSongInfo(song) - songInfo.show() + binding.songInfo.text = getSongInfo(song) + binding.songInfo.show() } else { - songInfo.hide() + binding.songInfo.hide() } } @@ -188,40 +200,46 @@ class FlatPlaybackControlsFragment : } private fun setUpRepeatButton() { - repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } + binding.repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } } override fun updateRepeatState() { when (MusicPlayerRemote.repeatMode) { MusicService.REPEAT_MODE_NONE -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter( + binding.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.repeatButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) } MusicService.REPEAT_MODE_ALL -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } MusicService.REPEAT_MODE_THIS -> { - repeatButton.setImageResource(R.drawable.ic_repeat_one) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat_one) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } } } private fun setUpShuffleButton() { - shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } + binding.shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } } override fun updateShuffleState() { when (MusicPlayerRemote.shuffleMode) { - MusicService.SHUFFLE_MODE_SHUFFLE -> shuffleButton.setColorFilter( + MusicService.SHUFFLE_MODE_SHUFFLE -> binding.shuffleButton.setColorFilter( lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) - else -> shuffleButton.setColorFilter( + else -> binding.shuffleButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) @@ -229,7 +247,7 @@ class FlatPlaybackControlsFragment : } override fun setUpProgressSlider() { - progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { + binding.progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) { MusicPlayerRemote.seekTo(progress) @@ -243,14 +261,19 @@ class FlatPlaybackControlsFragment : } override fun onUpdateProgressViews(progress: Int, total: Int) { - progressSlider.max = total + binding.progressSlider.max = total - val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) + val animator = ObjectAnimator.ofInt(binding.progressSlider, "progress", progress) animator.duration = SLIDER_ANIMATION_TIME animator.interpolator = LinearInterpolator() animator.start() - songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) - songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) + binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/flat/FlatPlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/flat/FlatPlayerFragment.kt index 6ed5f0fb..5cad96de 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/flat/FlatPlayerFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/flat/FlatPlayerFragment.kt @@ -25,6 +25,7 @@ import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentFlatPlayerBinding import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.helper.MusicPlayerRemote @@ -33,11 +34,10 @@ import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.ViewUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import code.name.monkey.retromusic.views.DrawableGradient -import kotlinx.android.synthetic.main.fragment_flat_player.* class FlatPlayerFragment : AbsPlayerFragment(R.layout.fragment_flat_player) { override fun playerToolbar(): Toolbar { - return playerToolbar + return binding.playerToolbar } private var valueAnimator: ValueAnimator? = null @@ -46,6 +46,10 @@ class FlatPlayerFragment : AbsPlayerFragment(R.layout.fragment_flat_player) { override val paletteColor: Int get() = lastColor + private var _binding: FragmentFlatPlayerBinding? = null + private val binding get() = _binding!! + + private fun setUpSubFragments() { controlsFragment = childFragmentManager.findFragmentById(R.id.playbackControlsFragment) as FlatPlaybackControlsFragment @@ -55,11 +59,11 @@ class FlatPlayerFragment : AbsPlayerFragment(R.layout.fragment_flat_player) { } private fun setUpPlayerToolbar() { - playerToolbar.inflateMenu(R.menu.menu_player) - playerToolbar.setNavigationOnClickListener { _ -> requireActivity().onBackPressed() } - playerToolbar.setOnMenuItemClickListener(this) + binding.playerToolbar.inflateMenu(R.menu.menu_player) + binding.playerToolbar.setNavigationOnClickListener { requireActivity().onBackPressed() } + binding.playerToolbar.setOnMenuItemClickListener(this) ToolbarContentTintHelper.colorizeToolbar( - playerToolbar, + binding.playerToolbar, ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal), requireActivity() ) @@ -76,13 +80,14 @@ class FlatPlayerFragment : AbsPlayerFragment(R.layout.fragment_flat_player) { GradientDrawable.Orientation.TOP_BOTTOM, intArrayOf(animation.animatedValue as Int, android.R.color.transparent), 0 ) - colorGradientBackground?.background = drawable + binding.colorGradientBackground.background = drawable } valueAnimator?.setDuration(ViewUtil.RETRO_MUSIC_ANIM_TIME.toLong())?.start() } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentFlatPlayerBinding.bind(view) setUpPlayerToolbar() setUpSubFragments() } @@ -117,7 +122,11 @@ class FlatPlayerFragment : AbsPlayerFragment(R.layout.fragment_flat_player) { MaterialValueHelper.getPrimaryTextColor(requireContext(), isLight) else ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal) - ToolbarContentTintHelper.colorizeToolbar(playerToolbar, iconColor, requireActivity()) + ToolbarContentTintHelper.colorizeToolbar( + binding.playerToolbar, + iconColor, + requireActivity() + ) if (PreferenceUtil.isAdaptiveColor) { colorize(color.backgroundColor) } @@ -133,4 +142,9 @@ class FlatPlayerFragment : AbsPlayerFragment(R.layout.fragment_flat_player) { updateIsFavorite() } } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/full/FullPlaybackControlsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/full/FullPlaybackControlsFragment.kt index d15a6d98..9dbfd766 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/full/FullPlaybackControlsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/full/FullPlaybackControlsFragment.kt @@ -15,13 +15,12 @@ package code.name.monkey.retromusic.fragments.player.full import android.animation.ObjectAnimator -import android.annotation.SuppressLint import android.content.Intent import android.content.res.ColorStateList import android.graphics.Color import android.graphics.PorterDuff +import android.graphics.drawable.AnimatedVectorDrawable import android.graphics.drawable.Drawable -import android.os.AsyncTask import android.os.Bundle import android.view.MenuItem import android.view.View @@ -31,8 +30,8 @@ import android.widget.PopupMenu import android.widget.SeekBar import androidx.lifecycle.lifecycleScope import code.name.monkey.appthemehelper.util.ColorUtil -import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentFullPlayerControlsBinding import code.name.monkey.retromusic.db.PlaylistEntity import code.name.monkey.retromusic.db.SongEntity import code.name.monkey.retromusic.db.toSongEntity @@ -42,6 +41,8 @@ import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.fragments.LibraryViewModel import code.name.monkey.retromusic.fragments.ReloadType import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment +import code.name.monkey.retromusic.fragments.base.goToAlbum +import code.name.monkey.retromusic.fragments.base.goToArtist import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler @@ -52,7 +53,6 @@ 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 kotlinx.android.synthetic.main.fragment_full_player_controls.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -70,6 +70,8 @@ class FullPlaybackControlsFragment : private var lastDisabledPlaybackControlsColor: Int = 0 private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper private val libraryViewModel: LibraryViewModel by sharedViewModel() + private var _binding: FragmentFullPlayerControlsBinding? = null + private val binding get() = _binding!! override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -78,11 +80,18 @@ class FullPlaybackControlsFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setUpMusicControllers() + _binding = FragmentFullPlayerControlsBinding.bind(view) - songTotalTime.setTextColor(Color.WHITE) - songCurrentProgress.setTextColor(Color.WHITE) - title.isSelected = true + setUpMusicControllers() + binding.songTotalTime.setTextColor(Color.WHITE) + binding.songCurrentProgress.setTextColor(Color.WHITE) + binding.title.isSelected = true + binding.title.setOnClickListener { + goToAlbum(requireActivity()) + } + binding.text.setOnClickListener { + goToArtist(requireActivity()) + } } override fun onResume() { @@ -96,7 +105,7 @@ class FullPlaybackControlsFragment : } public override fun show() { - playPauseButton!!.animate() + binding.playPauseButton.animate() .scaleX(1f) .scaleY(1f) .setInterpolator(DecelerateInterpolator()) @@ -104,7 +113,7 @@ class FullPlaybackControlsFragment : } public override fun hide() { - playPauseButton.apply { + binding.playPauseButton.apply { scaleX = 0f scaleY = 0f rotation = 0f @@ -116,18 +125,18 @@ class FullPlaybackControlsFragment : lastDisabledPlaybackControlsColor = ColorUtil.withAlpha(color.primaryTextColor, 0.3f) val tintList = ColorStateList.valueOf(color.primaryTextColor) - playerMenu.imageTintList = tintList - songFavourite.imageTintList = tintList + binding.playerMenu.imageTintList = tintList + binding.songFavourite.imageTintList = tintList volumeFragment?.setTintableColor(color.primaryTextColor) - progressSlider.applyColor(color.primaryTextColor) - title.setTextColor(color.primaryTextColor) - text.setTextColor(color.secondaryTextColor) - songInfo.setTextColor(color.secondaryTextColor) - songCurrentProgress.setTextColor(color.secondaryTextColor) - songTotalTime.setTextColor(color.secondaryTextColor) + binding.progressSlider.applyColor(color.primaryTextColor) + binding.title.setTextColor(color.primaryTextColor) + binding.text.setTextColor(color.secondaryTextColor) + binding.songInfo.setTextColor(color.secondaryTextColor) + binding.songCurrentProgress.setTextColor(color.secondaryTextColor) + binding.songTotalTime.setTextColor(color.secondaryTextColor) - playPauseButton.backgroundTintList = tintList - playPauseButton.imageTintList = ColorStateList.valueOf(color.backgroundColor) + binding.playPauseButton.backgroundTintList = tintList + binding.playPauseButton.imageTintList = ColorStateList.valueOf(color.backgroundColor) updateRepeatState() updateShuffleState() @@ -143,14 +152,14 @@ class FullPlaybackControlsFragment : private fun updateSong() { val song = MusicPlayerRemote.currentSong - title.text = song.title - text.text = song.artistName + binding.title.text = song.title + binding.text.text = song.artistName updateIsFavorite() if (PreferenceUtil.isSongInfo) { - songInfo.text = getSongInfo(song) - songInfo.show() + binding.songInfo.text = getSongInfo(song) + binding.songInfo.show() } else { - songInfo.hide() + binding.songInfo.hide() } } @@ -165,19 +174,17 @@ class FullPlaybackControlsFragment : private fun updatePlayPauseDrawableState() { if (MusicPlayerRemote.isPlaying) { - playPauseButton.setImageResource(R.drawable.ic_pause) + binding.playPauseButton.setImageResource(R.drawable.ic_pause) } else { - playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_32dp) + binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_32dp) } } private fun setUpPlayPauseFab() { - playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) - playPauseButton.post { - if (playPauseButton != null) { - playPauseButton.pivotX = (playPauseButton.width / 2).toFloat() - playPauseButton.pivotY = (playPauseButton.height / 2).toFloat() - } + binding.playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) + binding.playPauseButton.post { + binding.playPauseButton.pivotX = (binding.playPauseButton.width / 2).toFloat() + binding.playPauseButton.pivotY = (binding.playPauseButton.height / 2).toFloat() } } @@ -192,10 +199,11 @@ class FullPlaybackControlsFragment : } private fun setupMenu() { - playerMenu.setOnClickListener { + binding.playerMenu.setOnClickListener { val popupMenu = PopupMenu(requireContext(), it) popupMenu.setOnMenuItemClickListener(this) popupMenu.inflate(R.menu.menu_player) + popupMenu.menu.findItem(R.id.action_toggle_favorite).isVisible = false popupMenu.show() } } @@ -206,17 +214,17 @@ class FullPlaybackControlsFragment : private fun setUpPrevNext() { updatePrevNextColor() - nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } - previousButton.setOnClickListener { MusicPlayerRemote.back() } + binding.nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } + binding.previousButton.setOnClickListener { MusicPlayerRemote.back() } } private fun updatePrevNextColor() { - nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) - previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) } override fun setUpProgressSlider() { - progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { + binding.progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) { MusicPlayerRemote.seekTo(progress) @@ -230,15 +238,15 @@ class FullPlaybackControlsFragment : } override fun onUpdateProgressViews(progress: Int, total: Int) { - progressSlider.max = total + binding.progressSlider.max = total - val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) + val animator = ObjectAnimator.ofInt(binding.progressSlider, "progress", progress) animator.duration = SLIDER_ANIMATION_TIME animator.interpolator = LinearInterpolator() animator.start() - songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) - songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) + binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) } override fun onRepeatModeChanged() { @@ -250,16 +258,16 @@ class FullPlaybackControlsFragment : } private fun setUpShuffleButton() { - shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } + binding.shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } } override fun updateShuffleState() { when (MusicPlayerRemote.shuffleMode) { - MusicService.SHUFFLE_MODE_SHUFFLE -> shuffleButton.setColorFilter( + MusicService.SHUFFLE_MODE_SHUFFLE -> binding.shuffleButton.setColorFilter( lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) - else -> shuffleButton.setColorFilter( + else -> binding.shuffleButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) @@ -267,36 +275,46 @@ class FullPlaybackControlsFragment : } private fun setUpRepeatButton() { - repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } + binding.repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } } override fun updateRepeatState() { when (MusicPlayerRemote.repeatMode) { MusicService.REPEAT_MODE_NONE -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter( + binding.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.repeatButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) } MusicService.REPEAT_MODE_ALL -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } MusicService.REPEAT_MODE_THIS -> { - repeatButton.setImageResource(R.drawable.ic_repeat_one) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat_one) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } } } private fun setupFavourite() { - songFavourite?.setOnClickListener { + binding.songFavourite.setOnClickListener { toggleFavorite(MusicPlayerRemote.currentSong) } } - fun updateIsFavorite() { + override fun onFavoriteStateChanged() { + updateIsFavorite(animate = true) + } + + fun updateIsFavorite(animate: Boolean = false) { lifecycleScope.launch(Dispatchers.IO) { val playlist: PlaylistEntity? = libraryViewModel.favoritePlaylist() if (playlist != null) { @@ -304,19 +322,30 @@ class FullPlaybackControlsFragment : MusicPlayerRemote.currentSong.toSongEntity(playlist.playListId) val isFavorite: Boolean = libraryViewModel.isFavoriteSong(song).isNotEmpty() withContext(Dispatchers.Main) { - val icon = + val icon = if (animate) { + if (isFavorite) R.drawable.avd_favorite else R.drawable.avd_unfavorite + } else { if (isFavorite) R.drawable.ic_favorite else R.drawable.ic_favorite_border - val drawable = TintHelper.createTintedDrawable(activity, icon, Color.WHITE) - songFavourite?.setImageDrawable(drawable) + } + val drawable: Drawable? = RetroUtil.getTintedVectorDrawable( + requireContext(), + icon, + Color.WHITE + ) + binding.songFavourite.apply { + setImageDrawable(drawable) + getDrawable().also { + if (it is AnimatedVectorDrawable) { + it.start() + } + } + } } } } } private fun toggleFavorite(song: Song) { - if (song.id == MusicPlayerRemote.currentSong.id) { - updateIsFavorite() - } lifecycleScope.launch(Dispatchers.IO) { val playlist: PlaylistEntity? = libraryViewModel.favoritePlaylist() if (playlist != null) { @@ -336,4 +365,9 @@ class FullPlaybackControlsFragment : fun onFavoriteToggled() { toggleFavorite(MusicPlayerRemote.currentSong) } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/full/FullPlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/full/FullPlayerFragment.kt index 92514472..bb2eee36 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/full/FullPlayerFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/full/FullPlayerFragment.kt @@ -18,119 +18,29 @@ import android.content.res.ColorStateList import android.graphics.Color import android.os.Bundle import android.view.View -import android.widget.FrameLayout -import android.widget.TextView import androidx.appcompat.widget.Toolbar -import androidx.core.os.bundleOf -import androidx.navigation.fragment.findNavController import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper -import code.name.monkey.retromusic.EXTRA_ARTIST_ID import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentFullBinding import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.extensions.whichFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment +import code.name.monkey.retromusic.fragments.base.goToArtist import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment -import code.name.monkey.retromusic.glide.ArtistGlideRequest +import code.name.monkey.retromusic.glide.GlideApp +import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.helper.MusicPlayerRemote -import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.model.Song -import code.name.monkey.retromusic.model.lyrics.AbsSynchronizedLyrics -import code.name.monkey.retromusic.model.lyrics.Lyrics import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import com.bumptech.glide.Glide -import kotlinx.android.synthetic.main.fragment_full.* -class FullPlayerFragment : AbsPlayerFragment(R.layout.fragment_full), - MusicProgressViewUpdateHelper.Callback { - private lateinit var lyricsLayout: FrameLayout - private lateinit var lyricsLine1: TextView - private lateinit var lyricsLine2: TextView - - private var lyrics: Lyrics? = null - private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper - - override fun onUpdateProgressViews(progress: Int, total: Int) { - if (!isLyricsLayoutBound()) return - - if (!isLyricsLayoutVisible()) { - hideLyricsLayout() - return - } - - if (lyrics !is AbsSynchronizedLyrics) return - val synchronizedLyrics = lyrics as AbsSynchronizedLyrics - - lyricsLayout.visibility = View.VISIBLE - lyricsLayout.alpha = 1f - - val oldLine = lyricsLine2.text.toString() - val line = synchronizedLyrics.getLine(progress) - - if (oldLine != line || oldLine.isEmpty()) { - lyricsLine1.text = oldLine - lyricsLine2.text = line - - lyricsLine1.visibility = View.VISIBLE - lyricsLine2.visibility = View.VISIBLE - - lyricsLine2.measure( - View.MeasureSpec.makeMeasureSpec( - lyricsLine2.measuredWidth, - View.MeasureSpec.EXACTLY - ), - View.MeasureSpec.UNSPECIFIED - ) - val h: Float = lyricsLine2.measuredHeight.toFloat() - - lyricsLine1.alpha = 1f - lyricsLine1.translationY = 0f - lyricsLine1.animate().alpha(0f).translationY(-h).duration = VISIBILITY_ANIM_DURATION - - lyricsLine2.alpha = 0f - lyricsLine2.translationY = h - lyricsLine2.animate().alpha(1f).translationY(0f).duration = VISIBILITY_ANIM_DURATION - } - } - - private fun isLyricsLayoutVisible(): Boolean { - return lyrics != null && lyrics!!.isSynchronized && lyrics!!.isValid - } - - private fun isLyricsLayoutBound(): Boolean { - return lyricsLayout != null && lyricsLine1 != null && lyricsLine2 != null - } - - private fun hideLyricsLayout() { - lyricsLayout.animate().alpha(0f).setDuration(VISIBILITY_ANIM_DURATION) - .withEndAction(Runnable { - if (!isLyricsLayoutBound()) return@Runnable - lyricsLayout.visibility = View.GONE - lyricsLine1.text = null - lyricsLine2.text = null - }) - } - - override fun setLyrics(l: Lyrics?) { - lyrics = l - - if (!isLyricsLayoutBound()) return - - if (!isLyricsLayoutVisible()) { - hideLyricsLayout() - return - } - - lyricsLine1.text = null - lyricsLine2.text = null - - lyricsLayout.visibility = View.VISIBLE - lyricsLayout.animate().alpha(1f).duration = VISIBILITY_ANIM_DURATION - } +class FullPlayerFragment : AbsPlayerFragment(R.layout.fragment_full) { + private var _binding: FragmentFullBinding? = null + private val binding get() = _binding!! override fun playerToolbar(): Toolbar { - return playerToolbar + return binding.playerToolbar } private var lastColor: Int = 0 @@ -139,33 +49,24 @@ class FullPlayerFragment : AbsPlayerFragment(R.layout.fragment_full), private lateinit var controlsFragment: FullPlaybackControlsFragment private fun setUpPlayerToolbar() { - playerToolbar.apply { + binding.playerToolbar.apply { setNavigationOnClickListener { requireActivity().onBackPressed() } } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - lyricsLayout = view.findViewById(R.id.playerLyrics) - lyricsLine1 = view.findViewById(R.id.player_lyrics_line1) - lyricsLine2 = view.findViewById(R.id.player_lyrics_line2) + _binding = FragmentFullBinding.bind(view) setUpSubFragments() setUpPlayerToolbar() setupArtist() - nextSong.isSelected = true - - progressViewUpdateHelper = MusicProgressViewUpdateHelper(this, 500, 1000) - progressViewUpdateHelper.start() + binding.nextSong.isSelected = true } private fun setupArtist() { - artistImage.setOnClickListener { - mainActivity.collapsePanel() - findNavController().navigate( - R.id.artistDetailsFragment, - bundleOf(EXTRA_ARTIST_ID to MusicPlayerRemote.currentSong.artistId), - ) + binding.artistImage.setOnClickListener { + goToArtist(mainActivity) } } @@ -192,10 +93,10 @@ class FullPlayerFragment : AbsPlayerFragment(R.layout.fragment_full), override fun onColorChanged(color: MediaNotificationProcessor) { lastColor = color.backgroundColor - mask.backgroundTintList = ColorStateList.valueOf(color.backgroundColor) + binding.mask.backgroundTintList = ColorStateList.valueOf(color.backgroundColor) controlsFragment.setColor(color) libraryViewModel.updateColor(color.backgroundColor) - ToolbarContentTintHelper.colorizeToolbar(playerToolbar, Color.WHITE, activity) + ToolbarContentTintHelper.colorizeToolbar(binding.playerToolbar, Color.WHITE, activity) } override fun onFavoriteToggled() { @@ -224,16 +125,15 @@ class FullPlayerFragment : AbsPlayerFragment(R.layout.fragment_full), override fun onDestroyView() { super.onDestroyView() - progressViewUpdateHelper.stop() + _binding = null } private fun updateArtistImage() { libraryViewModel.artist(MusicPlayerRemote.currentSong.artistId) .observe(viewLifecycleOwner, { artist -> - ArtistGlideRequest.Builder.from(Glide.with(requireContext()), artist) - .generatePalette(requireContext()) - .build() - .into(object : RetroMusicColoredTarget(artistImage) { + GlideApp.with(requireActivity()).asBitmapPalette().artistImageOptions(artist) + .load(RetroGlideExtension.getArtistModel(artist)) + .into(object : RetroMusicColoredTarget(binding.artistImage) { override fun onColorReady(colors: MediaNotificationProcessor) { } }) @@ -248,12 +148,12 @@ class FullPlayerFragment : AbsPlayerFragment(R.layout.fragment_full), private fun updateLabel() { (MusicPlayerRemote.playingQueue.size - 1).apply { if (this == (MusicPlayerRemote.position)) { - nextSongLabel.setText(R.string.last_song) - nextSong.hide() + binding.nextSongLabel.setText(R.string.last_song) + binding.nextSong.hide() } else { val title = MusicPlayerRemote.playingQueue[MusicPlayerRemote.position + 1].title - nextSongLabel.setText(R.string.next_song) - nextSong.apply { + binding.nextSongLabel.setText(R.string.next_song) + binding.nextSong.apply { text = title show() } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/gradient/GradientPlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/gradient/GradientPlayerFragment.kt index cf11bd78..68725bab 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/gradient/GradientPlayerFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/gradient/GradientPlayerFragment.kt @@ -19,6 +19,7 @@ import android.annotation.SuppressLint import android.content.res.ColorStateList import android.graphics.Color import android.graphics.PorterDuff +import android.graphics.drawable.AnimatedVectorDrawable import android.os.Bundle import android.view.View import android.view.animation.LinearInterpolator @@ -35,6 +36,7 @@ import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.retromusic.R import code.name.monkey.retromusic.RetroBottomSheetBehavior import code.name.monkey.retromusic.adapter.song.PlayingQueueAdapter +import code.name.monkey.retromusic.databinding.FragmentGradientPlayerBinding import code.name.monkey.retromusic.db.PlaylistEntity import code.name.monkey.retromusic.db.SongEntity import code.name.monkey.retromusic.db.toSongEntity @@ -44,11 +46,12 @@ import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.fragments.VolumeFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment +import code.name.monkey.retromusic.fragments.base.goToAlbum +import code.name.monkey.retromusic.fragments.base.goToArtist import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener -import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.PreferenceUtil @@ -60,9 +63,6 @@ import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropM import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils -import kotlinx.android.synthetic.main.fragment_gradient_controls.* -import kotlinx.android.synthetic.main.fragment_gradient_player.* -import kotlinx.android.synthetic.main.status_bar.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -82,14 +82,17 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play private var playingQueueAdapter: PlayingQueueAdapter? = null private lateinit var linearLayoutManager: LinearLayoutManager + private var _binding: FragmentGradientPlayerBinding? = null + private val binding get() = _binding!! + private val bottomSheetCallbackList = object : BottomSheetCallback() { override fun onSlide(bottomSheet: View, slideOffset: Float) { mainActivity.getBottomSheetBehavior().setAllowDragging(false) - playerQueueSheet.setPadding( - playerQueueSheet.paddingLeft, - (slideOffset * status_bar.height).toInt(), - playerQueueSheet.paddingRight, - playerQueueSheet.paddingBottom + binding.playerQueueSheet.setPadding( + binding.playerQueueSheet.paddingLeft, + (slideOffset * binding.statusBarLayout.statusBar.height).toInt(), + binding.playerQueueSheet.paddingRight, + binding.playerQueueSheet.paddingBottom ) } @@ -111,23 +114,24 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play } private fun setupFavourite() { - songFavourite.setOnClickListener { + binding.playbackControlsFragment.songFavourite.setOnClickListener { toggleFavorite(MusicPlayerRemote.currentSong) } } private fun setupMenu() { - playerMenu.setOnClickListener { + binding.playbackControlsFragment.playerMenu.setOnClickListener { val popupMenu = PopupMenu(requireContext(), it) popupMenu.setOnMenuItemClickListener(this) popupMenu.inflate(R.menu.menu_player) + popupMenu.menu.findItem(R.id.action_toggle_favorite).isVisible = false popupMenu.show() } } private fun setupPanel() { - if (!ViewCompat.isLaidOut(colorBackground) || colorBackground.isLayoutRequested) { - colorBackground.addOnLayoutChangeListener(this) + if (!ViewCompat.isLaidOut(binding.colorBackground) || binding.colorBackground.isLayoutRequested) { + binding.colorBackground.addOnLayoutChangeListener(this) return } } @@ -139,6 +143,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentGradientPlayerBinding.bind(view) hideVolumeIfAvailable() setUpMusicControllers() setupPanel() @@ -146,12 +151,18 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play setupSheet() setupMenu() setupFavourite() + binding.playbackControlsFragment.title.setOnClickListener { + goToAlbum(requireActivity()) + } + binding.playbackControlsFragment.text.setOnClickListener { + goToArtist(requireActivity()) + } } @SuppressLint("ClickableViewAccessibility") private fun setupSheet() { getQueuePanel().addBottomSheetCallback(bottomSheetCallbackList) - playerQueueSheet.setOnTouchListener { _, _ -> + binding.playerQueueSheet.setOnTouchListener { _, _ -> mainActivity.getBottomSheetBehavior().setAllowDragging(false) getQueuePanel().setAllowDragging(true) return@setOnTouchListener false @@ -159,7 +170,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play } private fun getQueuePanel(): RetroBottomSheetBehavior { - return RetroBottomSheetBehavior.from(playerQueueSheet) as RetroBottomSheetBehavior + return RetroBottomSheetBehavior.from(binding.playerQueueSheet) as RetroBottomSheetBehavior } override fun onResume() { @@ -184,6 +195,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play } override fun onBackPressed(): Boolean { + println("OK") var wasExpanded = false if (getQueuePanel().state == STATE_EXPANDED) { wasExpanded = getQueuePanel().state == STATE_EXPANDED @@ -203,46 +215,62 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play override fun onColorChanged(color: MediaNotificationProcessor) { lastColor = color.backgroundColor libraryViewModel.updateColor(color.backgroundColor) - mask.backgroundTintList = ColorStateList.valueOf(color.backgroundColor) - colorBackground.setBackgroundColor(color.backgroundColor) - playerQueueSheet.setBackgroundColor(ColorUtil.darkenColor(color.backgroundColor)) + binding.mask.backgroundTintList = ColorStateList.valueOf(color.backgroundColor) + binding.colorBackground.setBackgroundColor(color.backgroundColor) + binding.playerQueueSheet.setBackgroundColor(ColorUtil.darkenColor(color.backgroundColor)) lastPlaybackControlsColor = color.primaryTextColor lastDisabledPlaybackControlsColor = ColorUtil.withAlpha(color.primaryTextColor, 0.3f) - title.setTextColor(lastPlaybackControlsColor) - text.setTextColor(lastDisabledPlaybackControlsColor) - playPauseButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) - nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) - previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) - songFavourite.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) - queueIcon.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) - playerMenu.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) - songCurrentProgress.setTextColor(lastDisabledPlaybackControlsColor) - songTotalTime.setTextColor(lastDisabledPlaybackControlsColor) - nextSong.setTextColor(lastPlaybackControlsColor) - songInfo.setTextColor(lastDisabledPlaybackControlsColor) + binding.playbackControlsFragment.title.setTextColor(lastPlaybackControlsColor) + binding.playbackControlsFragment.text.setTextColor(lastDisabledPlaybackControlsColor) + binding.playbackControlsFragment.playPauseButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) + binding.playbackControlsFragment.nextButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) + binding.playbackControlsFragment.previousButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) + binding.playbackControlsFragment.songFavourite.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) + binding.queueIcon.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.playbackControlsFragment.playerMenu.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) + binding.playbackControlsFragment.songCurrentProgress.setTextColor( + lastDisabledPlaybackControlsColor + ) + binding.playbackControlsFragment.songTotalTime.setTextColor( + lastDisabledPlaybackControlsColor + ) + binding.nextSong.setTextColor(lastPlaybackControlsColor) + binding.playbackControlsFragment.songInfo.setTextColor(lastDisabledPlaybackControlsColor) volumeFragment?.setTintableColor(lastPlaybackControlsColor.ripAlpha()) - ViewUtil.setProgressDrawable(progressSlider, lastPlaybackControlsColor.ripAlpha(), true) + ViewUtil.setProgressDrawable( + binding.playbackControlsFragment.progressSlider, + lastPlaybackControlsColor.ripAlpha(), + true + ) updateRepeatState() updateShuffleState() updatePrevNextColor() } - override fun toggleFavorite(song: Song) { - super.toggleFavorite(song) - if (song.id == MusicPlayerRemote.currentSong.id) { - updateIsFavoriteIcon() - } - } - override fun onFavoriteToggled() { toggleFavorite(MusicPlayerRemote.currentSong) } - private fun updateIsFavoriteIcon() { + private fun updateIsFavoriteIcon(animate: Boolean = false) { lifecycleScope.launch(Dispatchers.IO) { val playlist: PlaylistEntity? = libraryViewModel.favoritePlaylist() if (playlist != null) { @@ -250,10 +278,19 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play MusicPlayerRemote.currentSong.toSongEntity(playlist.playListId) val isFavorite: Boolean = libraryViewModel.isFavoriteSong(song).isNotEmpty() withContext(Dispatchers.Main) { - val icon = - if (isFavorite) R.drawable.ic_favorite - else R.drawable.ic_favorite_border - songFavourite.setImageResource(icon) + val icon = if (animate) { + if (isFavorite) R.drawable.avd_favorite else R.drawable.avd_unfavorite + } else { + if (isFavorite) R.drawable.ic_favorite else R.drawable.ic_favorite_border + } + binding.playbackControlsFragment.songFavourite.apply { + setImageResource(icon) + drawable.also { + if (it is AnimatedVectorDrawable) { + it.start() + } + } + } } } } @@ -298,6 +335,10 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play updateIsFavoriteIcon() } + override fun onFavoriteStateChanged() { + updateIsFavoriteIcon(animate = true) + } + override fun onQueueChanged() { super.onQueueChanged() updateLabel() @@ -306,14 +347,14 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play private fun updateSong() { val song = MusicPlayerRemote.currentSong - title.text = song.title - text.text = song.artistName + binding.playbackControlsFragment.title.text = song.title + binding.playbackControlsFragment.text.text = song.artistName updateLabel() if (PreferenceUtil.isSongInfo) { - songInfo.text = getSongInfo(song) - songInfo.show() + binding.playbackControlsFragment.songInfo.text = getSongInfo(song) + binding.playbackControlsFragment.songInfo.show() } else { - songInfo.hide() + binding.playbackControlsFragment.songInfo.hide() } } @@ -323,45 +364,53 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play setUpRepeatButton() setUpShuffleButton() setUpProgressSlider() - title.isSelected = true - text.isSelected = true + binding.playbackControlsFragment.title.isSelected = true + binding.playbackControlsFragment.text.isSelected = true } private fun updatePlayPauseDrawableState() { if (MusicPlayerRemote.isPlaying) { - playPauseButton.setImageResource(R.drawable.ic_pause_white_64dp) + binding.playbackControlsFragment.playPauseButton.setImageResource(R.drawable.ic_pause_white_64dp) } else { - playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_64dp) + binding.playbackControlsFragment.playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_64dp) } } private fun setUpPlayPauseFab() { - playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) + binding.playbackControlsFragment.playPauseButton.setOnClickListener( + PlayPauseButtonOnClickHandler() + ) } private fun setUpPrevNext() { updatePrevNextColor() - nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } - previousButton.setOnClickListener { MusicPlayerRemote.back() } + binding.playbackControlsFragment.nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } + binding.playbackControlsFragment.previousButton.setOnClickListener { MusicPlayerRemote.back() } } private fun updatePrevNextColor() { - nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) - previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.playbackControlsFragment.nextButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) + binding.playbackControlsFragment.previousButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } private fun setUpShuffleButton() { - shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } + binding.shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } } fun updateShuffleState() { when (MusicPlayerRemote.shuffleMode) { MusicService.SHUFFLE_MODE_SHUFFLE -> - shuffleButton.setColorFilter( + binding.shuffleButton.setColorFilter( lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) - else -> shuffleButton.setColorFilter( + else -> binding.shuffleButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) @@ -369,25 +418,31 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play } private fun setUpRepeatButton() { - repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } + binding.repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } } fun updateRepeatState() { when (MusicPlayerRemote.repeatMode) { MusicService.REPEAT_MODE_NONE -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter( + binding.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.repeatButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) } MusicService.REPEAT_MODE_ALL -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } MusicService.REPEAT_MODE_THIS -> { - repeatButton.setImageResource(R.drawable.ic_repeat_one) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat_one) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } } } @@ -395,10 +450,10 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play private fun updateLabel() { (MusicPlayerRemote.playingQueue.size - 1).apply { if (this == (MusicPlayerRemote.position)) { - nextSong.text = "Last song" + binding.nextSong.text = "Last song" } else { val title = MusicPlayerRemote.playingQueue[MusicPlayerRemote.position + 1].title - nextSong.text = title + binding.nextSong.text = title } } } @@ -415,7 +470,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play oldBottom: Int ) { val panel = getQueuePanel() - panel.peekHeight = container.height + panel.peekHeight = binding.container.height } private fun setupRecyclerView() { @@ -436,12 +491,14 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play recyclerViewDragDropManager?.createWrappedAdapter(playingQueueAdapter!!) as RecyclerView.Adapter<*> wrappedAdapter = recyclerViewSwipeManager?.createWrappedAdapter(wrappedAdapter) as RecyclerView.Adapter<*> - recyclerView.layoutManager = linearLayoutManager - recyclerView.adapter = wrappedAdapter - recyclerView.itemAnimator = animator - recyclerViewTouchActionGuardManager?.attachRecyclerView(recyclerView) - recyclerViewDragDropManager?.attachRecyclerView(recyclerView) - recyclerViewSwipeManager?.attachRecyclerView(recyclerView) + binding.recyclerView.apply { + layoutManager = linearLayoutManager + adapter = wrappedAdapter + itemAnimator = animator + recyclerViewTouchActionGuardManager?.attachRecyclerView(this) + recyclerViewDragDropManager?.attachRecyclerView(this) + recyclerViewSwipeManager?.attachRecyclerView(this) + } linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0) } @@ -460,6 +517,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play } WrapperAdapterUtils.releaseAll(wrappedAdapter) + _binding = null } private fun updateQueuePosition() { @@ -473,12 +531,13 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play } private fun resetToCurrentPosition() { - recyclerView.stopScroll() + binding.recyclerView.stopScroll() linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0) } fun setUpProgressSlider() { - progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { + binding.playbackControlsFragment.progressSlider.setOnSeekBarChangeListener(object : + SimpleOnSeekbarChangeListener() { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) { MusicPlayerRemote.seekTo(progress) @@ -492,12 +551,18 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play } override fun onUpdateProgressViews(progress: Int, total: Int) { - progressSlider.max = total - val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) + binding.playbackControlsFragment.progressSlider.max = total + val animator = ObjectAnimator.ofInt( + binding.playbackControlsFragment.progressSlider, + "progress", + progress + ) animator.duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME animator.interpolator = LinearInterpolator() animator.start() - songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) - songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + binding.playbackControlsFragment.songTotalTime.text = + MusicUtil.getReadableDurationString(total.toLong()) + binding.playbackControlsFragment.songCurrentProgress.text = + MusicUtil.getReadableDurationString(progress.toLong()) } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/home/HomePlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/home/HomePlayerFragment.kt index c2925301..d32716d2 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/home/HomePlayerFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/home/HomePlayerFragment.kt @@ -21,19 +21,23 @@ import androidx.appcompat.widget.Toolbar import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentHomePlayerBinding import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import kotlinx.android.synthetic.main.fragment_home_player.* class HomePlayerFragment : AbsPlayerFragment(R.layout.fragment_home_player), MusicProgressViewUpdateHelper.Callback { private var lastColor: Int = 0 private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper + private var _binding: FragmentHomePlayerBinding? = null + private val binding get() = _binding!! + + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) progressViewUpdateHelper = MusicProgressViewUpdateHelper(this) @@ -41,6 +45,7 @@ class HomePlayerFragment : AbsPlayerFragment(R.layout.fragment_home_player), override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentHomePlayerBinding.bind(view) setUpPlayerToolbar() } @@ -54,8 +59,8 @@ class HomePlayerFragment : AbsPlayerFragment(R.layout.fragment_home_player), progressViewUpdateHelper.stop() } - override fun playerToolbar(): Toolbar? { - return playerToolbar + override fun playerToolbar(): Toolbar { + return binding.playerToolbar } override fun onShow() { @@ -76,8 +81,8 @@ class HomePlayerFragment : AbsPlayerFragment(R.layout.fragment_home_player), private fun updateSong() { val song = MusicPlayerRemote.currentSong - title.text = song.title - text.text = song.artistName + binding.title.text = song.title + binding.text.text = song.artistName } override fun onBackPressed(): Boolean { @@ -95,7 +100,7 @@ class HomePlayerFragment : AbsPlayerFragment(R.layout.fragment_home_player), lastColor = color.backgroundColor libraryViewModel.updateColor(color.backgroundColor) ToolbarContentTintHelper.colorizeToolbar( - playerToolbar, + binding.playerToolbar, Color.WHITE, requireActivity() ) @@ -113,18 +118,23 @@ class HomePlayerFragment : AbsPlayerFragment(R.layout.fragment_home_player), } override fun onUpdateProgressViews(progress: Int, total: Int) { - songTotalTime.text = MusicUtil.getReadableDurationString(progress.toLong()) + binding.songTotalTime.text = MusicUtil.getReadableDurationString(progress.toLong()) } private fun setUpPlayerToolbar() { - playerToolbar.inflateMenu(R.menu.menu_player) - playerToolbar.setNavigationOnClickListener { requireActivity().onBackPressed() } - playerToolbar.setOnMenuItemClickListener(this) + binding.playerToolbar.inflateMenu(R.menu.menu_player) + binding.playerToolbar.setNavigationOnClickListener { requireActivity().onBackPressed() } + binding.playerToolbar.setOnMenuItemClickListener(this) ToolbarContentTintHelper.colorizeToolbar( - playerToolbar, + binding.playerToolbar, ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal), requireActivity() ) } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/lockscreen/LockScreenControlsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/lockscreen/LockScreenControlsFragment.kt index 40612a61..9e9df329 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/lockscreen/LockScreenControlsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/lockscreen/LockScreenControlsFragment.kt @@ -26,6 +26,7 @@ import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentLockScreenPlaybackControlsBinding import code.name.monkey.retromusic.extensions.applyColor import code.name.monkey.retromusic.extensions.ripAlpha import code.name.monkey.retromusic.extensions.textColorSecondary @@ -38,7 +39,6 @@ import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import kotlinx.android.synthetic.main.fragment_lock_screen_playback_controls.* /** * @author Hemanth S (h4h13). @@ -50,6 +50,10 @@ class LockScreenControlsFragment : private var lastPlaybackControlsColor: Int = 0 private var lastDisabledPlaybackControlsColor: Int = 0 + private var _binding: FragmentLockScreenPlaybackControlsBinding? = null + private val binding get() = _binding!! + + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) progressViewUpdateHelper = MusicProgressViewUpdateHelper(this) @@ -57,14 +61,15 @@ class LockScreenControlsFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentLockScreenPlaybackControlsBinding.bind(view) setUpMusicControllers() - title.isSelected = true + binding.title.isSelected = true } private fun updateSong() { val song = MusicPlayerRemote.currentSong - title.text = song.title - text.text = String.format("%s - %s", song.artistName, song.albumName) + binding.title.text = song.title + binding.text.text = String.format("%s - %s", song.artistName, song.albumName) } override fun onResume() { @@ -123,32 +128,32 @@ class LockScreenControlsFragment : }.ripAlpha() volumeFragment?.setTintable(colorFinal) - progressSlider.applyColor(colorFinal) + binding.progressSlider.applyColor(colorFinal) updateRepeatState() updateShuffleState() updatePrevNextColor() val isDark = ColorUtil.isColorLight(colorFinal) - text.setTextColor(colorFinal) + binding.text.setTextColor(colorFinal) TintHelper.setTintAuto( - playPauseButton, + binding.playPauseButton, MaterialValueHelper.getPrimaryTextColor(requireContext(), isDark), false ) - TintHelper.setTintAuto(playPauseButton, colorFinal, true) + TintHelper.setTintAuto(binding.playPauseButton, colorFinal, true) } private fun setUpPlayPauseFab() { - playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) + binding.playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) } private fun updatePlayPauseDrawableState() { if (MusicPlayerRemote.isPlaying) { - playPauseButton.setImageResource(R.drawable.ic_pause) + binding.playPauseButton.setImageResource(R.drawable.ic_pause) } else { - playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_32dp) + binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_32dp) } } @@ -162,26 +167,26 @@ class LockScreenControlsFragment : private fun setUpPrevNext() { updatePrevNextColor() - nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } - previousButton.setOnClickListener { MusicPlayerRemote.back() } + binding.nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } + binding.previousButton.setOnClickListener { MusicPlayerRemote.back() } } private fun updatePrevNextColor() { - nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) - previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) } private fun setUpShuffleButton() { - shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } + binding.shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } } override fun updateShuffleState() { when (MusicPlayerRemote.shuffleMode) { - MusicService.SHUFFLE_MODE_SHUFFLE -> shuffleButton.setColorFilter( + MusicService.SHUFFLE_MODE_SHUFFLE -> binding.shuffleButton.setColorFilter( lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) - else -> shuffleButton.setColorFilter( + else -> binding.shuffleButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) @@ -189,31 +194,37 @@ class LockScreenControlsFragment : } private fun setUpRepeatButton() { - repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } + binding.repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } } override fun updateRepeatState() { when (MusicPlayerRemote.repeatMode) { MusicService.REPEAT_MODE_NONE -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter( + binding.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.repeatButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) } MusicService.REPEAT_MODE_ALL -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } MusicService.REPEAT_MODE_THIS -> { - repeatButton.setImageResource(R.drawable.ic_repeat_one) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat_one) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } } } public override fun show() { - playPauseButton!!.animate() + binding.playPauseButton.animate() .scaleX(1f) .scaleY(1f) .rotation(360f) @@ -222,17 +233,15 @@ class LockScreenControlsFragment : } public override fun hide() { - if (playPauseButton != null) { - playPauseButton!!.apply { - scaleX = 0f - scaleY = 0f - rotation = 0f - } + binding.playPauseButton.apply { + scaleX = 0f + scaleY = 0f + rotation = 0f } } override fun setUpProgressSlider() { - progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { + binding.progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) { MusicPlayerRemote.seekTo(progress) @@ -246,14 +255,19 @@ class LockScreenControlsFragment : } override fun onUpdateProgressViews(progress: Int, total: Int) { - progressSlider.max = total + binding.progressSlider.max = total - val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) + val animator = ObjectAnimator.ofInt(binding.progressSlider, "progress", progress) animator.duration = SLIDER_ANIMATION_TIME animator.interpolator = LinearInterpolator() animator.start() - songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) - songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) + binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/material/MaterialControlsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/material/MaterialControlsFragment.kt index ce3fea96..2be4a5c6 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/material/MaterialControlsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/material/MaterialControlsFragment.kt @@ -20,11 +20,15 @@ import android.os.Bundle import android.view.View import android.view.animation.LinearInterpolator import android.widget.SeekBar +import androidx.core.content.ContextCompat import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentMaterialPlaybackControlsBinding import code.name.monkey.retromusic.extensions.* import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment +import code.name.monkey.retromusic.fragments.base.goToAlbum +import code.name.monkey.retromusic.fragments.base.goToArtist import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler @@ -33,7 +37,6 @@ import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import kotlinx.android.synthetic.main.fragment_material_playback_controls.* /** * @author Hemanth S (h4h13). @@ -44,6 +47,9 @@ class MaterialControlsFragment : private var lastPlaybackControlsColor: Int = 0 private var lastDisabledPlaybackControlsColor: Int = 0 private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper + private var _binding: FragmentMaterialPlaybackControlsBinding? = null + private val binding get() = _binding!! + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -52,21 +58,28 @@ class MaterialControlsFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentMaterialPlaybackControlsBinding.bind(view) setUpMusicControllers() - title.isSelected = true - text.isSelected = true + binding.title.isSelected = true + binding.text.isSelected = true + binding.title.setOnClickListener { + goToAlbum(requireActivity()) + } + binding.text.setOnClickListener { + goToArtist(requireActivity()) + } } private fun updateSong() { val song = MusicPlayerRemote.currentSong - title.text = song.title - text.text = song.artistName + binding.title.text = song.title + binding.text.text = song.artistName if (PreferenceUtil.isSongInfo) { - songInfo.text = getSongInfo(song) - songInfo.show() + binding.songInfo.text = getSongInfo(song) + binding.songInfo.show() } else { - songInfo.hide() + binding.songInfo.hide() } } @@ -125,8 +138,8 @@ class MaterialControlsFragment : textColorSecondary() }.ripAlpha() - text.setTextColor(colorFinal) - progressSlider.applyColor(colorFinal) + binding.text.setTextColor(colorFinal) + binding.progressSlider.applyColor(colorFinal) volumeFragment?.setTintable(colorFinal) @@ -137,18 +150,28 @@ class MaterialControlsFragment : } private fun updatePlayPauseColor() { - playPauseButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.playPauseButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) } private fun setUpPlayPauseFab() { - playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) + binding.playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) } private fun updatePlayPauseDrawableState() { if (MusicPlayerRemote.isPlaying) { - playPauseButton.setImageResource(R.drawable.ic_pause_sharp_white_64dp) - } else { - playPauseButton.setImageResource(R.drawable.ic_play_arrow_sharp_white_64dp) + binding.playPauseButton.setImageDrawable( + ContextCompat.getDrawable( + requireContext(), + R.drawable.ic_pause_outline + ) + ) + } else if (!MusicPlayerRemote.isPlaying) { + binding.playPauseButton.setImageDrawable( + ContextCompat.getDrawable( + requireContext(), + R.drawable.ic_play_arrow_outline + ) + ) } } @@ -162,26 +185,26 @@ class MaterialControlsFragment : private fun setUpPrevNext() { updatePrevNextColor() - nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } - previousButton.setOnClickListener { MusicPlayerRemote.back() } + binding.nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } + binding.previousButton.setOnClickListener { MusicPlayerRemote.back() } } private fun updatePrevNextColor() { - nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) - previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) } private fun setUpShuffleButton() { - shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } + binding.shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } } override fun updateShuffleState() { when (MusicPlayerRemote.shuffleMode) { - MusicService.SHUFFLE_MODE_SHUFFLE -> shuffleButton.setColorFilter( + MusicService.SHUFFLE_MODE_SHUFFLE -> binding.shuffleButton.setColorFilter( lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) - else -> shuffleButton.setColorFilter( + else -> binding.shuffleButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) @@ -189,25 +212,31 @@ class MaterialControlsFragment : } private fun setUpRepeatButton() { - repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } + binding.repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } } override fun updateRepeatState() { when (MusicPlayerRemote.repeatMode) { MusicService.REPEAT_MODE_NONE -> { - repeatButton.setImageResource(R.drawable.ic_repeat_sharp) - repeatButton.setColorFilter( + binding.repeatButton.setImageResource(R.drawable.ic_repeat_sharp) + binding.repeatButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) } MusicService.REPEAT_MODE_ALL -> { - repeatButton.setImageResource(R.drawable.ic_repeat_sharp) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat_sharp) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } MusicService.REPEAT_MODE_THIS -> { - repeatButton.setImageResource(R.drawable.ic_repeat_one_sharp) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat_one_sharp) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } } } @@ -219,7 +248,7 @@ class MaterialControlsFragment : } override fun setUpProgressSlider() { - progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { + binding.progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) { MusicPlayerRemote.seekTo(progress) @@ -233,14 +262,19 @@ class MaterialControlsFragment : } override fun onUpdateProgressViews(progress: Int, total: Int) { - progressSlider.max = total + binding.progressSlider.max = total - val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) + val animator = ObjectAnimator.ofInt(binding.progressSlider, "progress", progress) animator.duration = SLIDER_ANIMATION_TIME animator.interpolator = LinearInterpolator() animator.start() - songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) - songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) + binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/material/MaterialFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/material/MaterialFragment.kt index eb98c5e1..888f6ad9 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/material/MaterialFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/material/MaterialFragment.kt @@ -20,13 +20,13 @@ import androidx.appcompat.widget.Toolbar import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentMaterialBinding import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.fragments.player.normal.PlayerFragment import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import kotlinx.android.synthetic.main.fragment_material.* /** * @author Hemanth S (h4h13). @@ -34,7 +34,7 @@ import kotlinx.android.synthetic.main.fragment_material.* class MaterialFragment : AbsPlayerFragment(R.layout.fragment_material) { override fun playerToolbar(): Toolbar { - return playerToolbar + return binding.playerToolbar } private var lastColor: Int = 0 @@ -44,6 +44,10 @@ class MaterialFragment : AbsPlayerFragment(R.layout.fragment_material) { private lateinit var playbackControlsFragment: MaterialControlsFragment + private var _binding: FragmentMaterialBinding? = null + private val binding get() = _binding!! + + override fun onShow() { playbackControlsFragment.show() } @@ -67,7 +71,7 @@ class MaterialFragment : AbsPlayerFragment(R.layout.fragment_material) { libraryViewModel.updateColor(color.backgroundColor) ToolbarContentTintHelper.colorizeToolbar( - playerToolbar, + binding.playerToolbar, ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal), requireActivity() ) @@ -86,6 +90,7 @@ class MaterialFragment : AbsPlayerFragment(R.layout.fragment_material) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentMaterialBinding.bind(view) setUpSubFragments() setUpPlayerToolbar() } @@ -99,7 +104,7 @@ class MaterialFragment : AbsPlayerFragment(R.layout.fragment_material) { } private fun setUpPlayerToolbar() { - playerToolbar.apply { + binding.playerToolbar.apply { inflateMenu(R.menu.menu_player) setNavigationOnClickListener { requireActivity().onBackPressed() } setOnMenuItemClickListener(this@MaterialFragment) @@ -125,4 +130,9 @@ class MaterialFragment : AbsPlayerFragment(R.layout.fragment_material) { return PlayerFragment() } } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/normal/PlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/normal/PlayerFragment.kt index b712e2c7..50a3a466 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/normal/PlayerFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/normal/PlayerFragment.kt @@ -23,6 +23,7 @@ import androidx.appcompat.widget.Toolbar import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentPlayerBinding import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.helper.MusicPlayerRemote @@ -31,7 +32,6 @@ import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.ViewUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import code.name.monkey.retromusic.views.DrawableGradient -import kotlinx.android.synthetic.main.fragment_player.* class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player) { @@ -42,6 +42,10 @@ class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player) { private lateinit var controlsFragment: PlayerPlaybackControlsFragment private var valueAnimator: ValueAnimator? = null + private var _binding: FragmentPlayerBinding? = null + private val binding get() = _binding!! + + private fun colorize(i: Int) { if (valueAnimator != null) { valueAnimator?.cancel() @@ -61,7 +65,7 @@ class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player) { ATHUtil.resolveColor(requireContext(), R.attr.colorSurface) ), 0 ) - colorGradientBackground?.background = drawable + binding.colorGradientBackground.background = drawable } } valueAnimator?.setDuration(ViewUtil.RETRO_MUSIC_ANIM_TIME.toLong())?.start() @@ -90,7 +94,7 @@ class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player) { libraryViewModel.updateColor(color.backgroundColor) ToolbarContentTintHelper.colorizeToolbar( - playerToolbar, + binding.playerToolbar, ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal), requireActivity() ) @@ -113,10 +117,16 @@ class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentPlayerBinding.bind(view) setUpSubFragments() setUpPlayerToolbar() } + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + private fun setUpSubFragments() { controlsFragment = childFragmentManager.findFragmentById(R.id.playbackControlsFragment) as PlayerPlaybackControlsFragment @@ -126,12 +136,13 @@ class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player) { } private fun setUpPlayerToolbar() { - playerToolbar.inflateMenu(R.menu.menu_player) - playerToolbar.setNavigationOnClickListener { requireActivity().onBackPressed() } - playerToolbar.setOnMenuItemClickListener(this) + binding.playerToolbar.inflateMenu(R.menu.menu_player) + //binding.playerToolbar.menu.setUpWithIcons() + binding.playerToolbar.setNavigationOnClickListener { requireActivity().onBackPressed() } + binding.playerToolbar.setOnMenuItemClickListener(this) ToolbarContentTintHelper.colorizeToolbar( - playerToolbar, + binding.playerToolbar, ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal), requireActivity() ) @@ -146,7 +157,7 @@ class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player) { } override fun playerToolbar(): Toolbar { - return playerToolbar + return binding.playerToolbar } companion object { diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/normal/PlayerPlaybackControlsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/normal/PlayerPlaybackControlsFragment.kt index ec5daaa9..79631258 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/normal/PlayerPlaybackControlsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/normal/PlayerPlaybackControlsFragment.kt @@ -27,11 +27,14 @@ import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentPlayerPlaybackControlsBinding import code.name.monkey.retromusic.extensions.applyColor import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.ripAlpha import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment +import code.name.monkey.retromusic.fragments.base.goToAlbum +import code.name.monkey.retromusic.fragments.base.goToArtist import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler @@ -40,7 +43,6 @@ import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import kotlinx.android.synthetic.main.fragment_player_playback_controls.* class PlayerPlaybackControlsFragment : AbsPlayerControlsFragment(R.layout.fragment_player_playback_controls) { @@ -48,6 +50,8 @@ class PlayerPlaybackControlsFragment : private var lastPlaybackControlsColor: Int = 0 private var lastDisabledPlaybackControlsColor: Int = 0 private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper + private var _binding: FragmentPlayerPlaybackControlsBinding? = null + private val binding get() = _binding!! override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -56,17 +60,24 @@ class PlayerPlaybackControlsFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentPlayerPlaybackControlsBinding.bind(view) setUpMusicControllers() - playPauseButton.setOnClickListener { + binding.playPauseButton.setOnClickListener { if (MusicPlayerRemote.isPlaying) { MusicPlayerRemote.pauseSong() } else { MusicPlayerRemote.resumePlaying() } - showBonceAnimation(playPauseButton) + showBounceAnimation(binding.playPauseButton) + } + binding.title.isSelected = true + binding.text.isSelected = true + binding.title.setOnClickListener { + goToAlbum(requireActivity()) + } + binding.text.setOnClickListener { + goToArtist(requireActivity()) } - title.isSelected = true - text.isSelected = true } override fun setColor(color: MediaNotificationProcessor) { @@ -90,15 +101,15 @@ class PlayerPlaybackControlsFragment : }.ripAlpha() TintHelper.setTintAuto( - playPauseButton, + binding.playPauseButton, MaterialValueHelper.getPrimaryTextColor( requireContext(), ColorUtil.isColorLight(colorFinal) ), false ) - TintHelper.setTintAuto(playPauseButton, colorFinal, true) - progressSlider.applyColor(colorFinal) + TintHelper.setTintAuto(binding.playPauseButton, colorFinal, true) + binding.progressSlider.applyColor(colorFinal) volumeFragment?.setTintable(colorFinal) updateRepeatState() updateShuffleState() @@ -107,14 +118,14 @@ class PlayerPlaybackControlsFragment : private fun updateSong() { val song = MusicPlayerRemote.currentSong - title.text = song.title - text.text = song.artistName + binding.title.text = song.title + binding.text.text = song.artistName if (PreferenceUtil.isSongInfo) { - songInfo.text = getSongInfo(song) - songInfo.show() + binding.songInfo.text = getSongInfo(song) + binding.songInfo.show() } else { - songInfo.hide() + binding.songInfo.hide() } } @@ -153,14 +164,14 @@ class PlayerPlaybackControlsFragment : } private fun setUpPlayPauseFab() { - playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) + binding.playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) } private fun updatePlayPauseDrawableState() { if (MusicPlayerRemote.isPlaying) { - playPauseButton.setImageResource(R.drawable.ic_pause) + binding.playPauseButton.setImageResource(R.drawable.ic_pause) } else { - playPauseButton.setImageResource(R.drawable.ic_play_arrow) + binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow) } } @@ -174,26 +185,26 @@ class PlayerPlaybackControlsFragment : private fun setUpPrevNext() { updatePrevNextColor() - nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } - previousButton.setOnClickListener { MusicPlayerRemote.back() } + binding.nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } + binding.previousButton.setOnClickListener { MusicPlayerRemote.back() } } private fun updatePrevNextColor() { - nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) - previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) } private fun setUpShuffleButton() { - shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } + binding.shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } } override fun updateShuffleState() { when (MusicPlayerRemote.shuffleMode) { - MusicService.SHUFFLE_MODE_SHUFFLE -> shuffleButton.setColorFilter( + MusicService.SHUFFLE_MODE_SHUFFLE -> binding.shuffleButton.setColorFilter( lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) - else -> shuffleButton.setColorFilter( + else -> binding.shuffleButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) @@ -201,31 +212,37 @@ class PlayerPlaybackControlsFragment : } private fun setUpRepeatButton() { - repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } + binding.repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } } override fun updateRepeatState() { when (MusicPlayerRemote.repeatMode) { MusicService.REPEAT_MODE_NONE -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter( + binding.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.repeatButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) } MusicService.REPEAT_MODE_ALL -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } MusicService.REPEAT_MODE_THIS -> { - repeatButton.setImageResource(R.drawable.ic_repeat_one) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat_one) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } } } public override fun show() { - playPauseButton.animate() + binding.playPauseButton.animate() .scaleX(1f) .scaleY(1f) .rotation(360f) @@ -234,7 +251,7 @@ class PlayerPlaybackControlsFragment : } public override fun hide() { - playPauseButton.apply { + binding.playPauseButton.apply { scaleX = 0f scaleY = 0f rotation = 0f @@ -242,7 +259,7 @@ class PlayerPlaybackControlsFragment : } override fun setUpProgressSlider() { - progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { + binding.progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) { MusicPlayerRemote.seekTo(progress) @@ -256,14 +273,19 @@ class PlayerPlaybackControlsFragment : } override fun onUpdateProgressViews(progress: Int, total: Int) { - progressSlider.max = total + binding.progressSlider.max = total - val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) + val animator = ObjectAnimator.ofInt(binding.progressSlider, "progress", progress) animator.duration = SLIDER_ANIMATION_TIME animator.interpolator = LinearInterpolator() animator.start() - songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) - songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) + binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/peak/PeakPlayerControlFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/peak/PeakPlayerControlFragment.kt index 578feb78..6bb06ccd 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/peak/PeakPlayerControlFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/peak/PeakPlayerControlFragment.kt @@ -26,6 +26,7 @@ import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentPeakControlPlayerBinding import code.name.monkey.retromusic.extensions.applyColor import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment import code.name.monkey.retromusic.helper.MusicPlayerRemote @@ -36,7 +37,6 @@ import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import kotlinx.android.synthetic.main.fragment_peak_control_player.* /** * Created by hemanths on 2019-10-04. @@ -47,6 +47,8 @@ class PeakPlayerControlFragment : AbsPlayerControlsFragment(R.layout.fragment_pe private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper private var lastPlaybackControlsColor: Int = 0 private var lastDisabledPlaybackControlsColor: Int = 0 + private var _binding: FragmentPeakControlPlayerBinding? = null + private val binding get() = _binding!! override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -68,6 +70,7 @@ class PeakPlayerControlFragment : AbsPlayerControlsFragment(R.layout.fragment_pe savedInstanceState: Bundle? ) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentPeakControlPlayerBinding.bind(view) setUpMusicControllers() } @@ -84,11 +87,11 @@ class PeakPlayerControlFragment : AbsPlayerControlsFragment(R.layout.fragment_pe } else { ThemeStore.accentColor(requireContext()) } - progressSlider.applyColor(controlsColor) + binding.progressSlider.applyColor(controlsColor) volumeFragment?.setTintableColor(controlsColor) - playPauseButton.setColorFilter(controlsColor, PorterDuff.Mode.SRC_IN) - nextButton.setColorFilter(controlsColor, PorterDuff.Mode.SRC_IN) - previousButton.setColorFilter(controlsColor, PorterDuff.Mode.SRC_IN) + binding.playPauseButton.setColorFilter(controlsColor, PorterDuff.Mode.SRC_IN) + binding.nextButton.setColorFilter(controlsColor, PorterDuff.Mode.SRC_IN) + binding.previousButton.setColorFilter(controlsColor, PorterDuff.Mode.SRC_IN) if (!ATHUtil.isWindowBackgroundDark(requireContext())) { lastPlaybackControlsColor = @@ -107,9 +110,9 @@ class PeakPlayerControlFragment : AbsPlayerControlsFragment(R.layout.fragment_pe private fun updatePlayPauseDrawableState() { if (MusicPlayerRemote.isPlaying) { - playPauseButton.setImageResource(R.drawable.ic_pause) + binding.playPauseButton.setImageResource(R.drawable.ic_pause) } else { - playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_32dp) + binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_32dp) } } @@ -122,19 +125,19 @@ class PeakPlayerControlFragment : AbsPlayerControlsFragment(R.layout.fragment_pe } private fun setUpShuffleButton() { - shuffleButton.setOnClickListener { + binding.shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } } private fun setUpRepeatButton() { - repeatButton.setOnClickListener { + binding.repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } } override fun setUpProgressSlider() { - progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { + binding.progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) { MusicPlayerRemote.seekTo(progress) @@ -148,41 +151,41 @@ class PeakPlayerControlFragment : AbsPlayerControlsFragment(R.layout.fragment_pe } override fun onUpdateProgressViews(progress: Int, total: Int) { - progressSlider.max = total + binding.progressSlider.max = total - val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) + val animator = ObjectAnimator.ofInt(binding.progressSlider, "progress", progress) animator.duration = SLIDER_ANIMATION_TIME animator.interpolator = LinearInterpolator() animator.start() - songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) - songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) + binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) } private fun setUpPlayPauseFab() { - TintHelper.setTintAuto(playPauseButton, Color.WHITE, true) - TintHelper.setTintAuto(playPauseButton, Color.BLACK, false) - playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) + TintHelper.setTintAuto(binding.playPauseButton, Color.WHITE, true) + TintHelper.setTintAuto(binding.playPauseButton, Color.BLACK, false) + binding.playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) } private fun setUpPrevNext() { updatePrevNextColor() - nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } - previousButton.setOnClickListener { MusicPlayerRemote.back() } + binding.nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } + binding.previousButton.setOnClickListener { MusicPlayerRemote.back() } } private fun updatePrevNextColor() { - nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) - previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) } override fun updateShuffleState() { when (MusicPlayerRemote.shuffleMode) { - MusicService.SHUFFLE_MODE_SHUFFLE -> shuffleButton.setColorFilter( + MusicService.SHUFFLE_MODE_SHUFFLE -> binding.shuffleButton.setColorFilter( lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) - else -> shuffleButton.setColorFilter( + else -> binding.shuffleButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) @@ -192,19 +195,25 @@ class PeakPlayerControlFragment : AbsPlayerControlsFragment(R.layout.fragment_pe override fun updateRepeatState() { when (MusicPlayerRemote.repeatMode) { MusicService.REPEAT_MODE_NONE -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter( + binding.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.repeatButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) } MusicService.REPEAT_MODE_ALL -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } MusicService.REPEAT_MODE_THIS -> { - repeatButton.setImageResource(R.drawable.ic_repeat_one) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat_one) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } } } @@ -226,4 +235,9 @@ class PeakPlayerControlFragment : AbsPlayerControlsFragment(R.layout.fragment_pe override fun onShuffleModeChanged() { updateShuffleState() } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/peak/PeakPlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/peak/PeakPlayerFragment.kt index 0f829906..3fe48ac4 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/peak/PeakPlayerFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/peak/PeakPlayerFragment.kt @@ -20,14 +20,16 @@ import androidx.appcompat.widget.Toolbar import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentPeakPlayerBinding import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment +import code.name.monkey.retromusic.fragments.base.goToAlbum +import code.name.monkey.retromusic.fragments.base.goToArtist import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import kotlinx.android.synthetic.main.fragment_peak_player.* /** * Created by hemanths on 2019-10-03. @@ -37,12 +39,22 @@ class PeakPlayerFragment : AbsPlayerFragment(R.layout.fragment_peak_player) { private lateinit var controlsFragment: PeakPlayerControlFragment private var lastColor: Int = 0 + private var _binding: FragmentPeakPlayerBinding? = null + private val binding get() = _binding!! + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentPeakPlayerBinding.bind(view) setUpPlayerToolbar() setUpSubFragments() - title.isSelected = true + binding.title.isSelected = true + binding.title.setOnClickListener { + goToAlbum(requireActivity()) + } + binding.text.setOnClickListener { + goToArtist(requireActivity()) + } } private fun setUpSubFragments() { @@ -55,7 +67,7 @@ class PeakPlayerFragment : AbsPlayerFragment(R.layout.fragment_peak_player) { } private fun setUpPlayerToolbar() { - playerToolbar.apply { + binding.playerToolbar.apply { inflateMenu(R.menu.menu_player) setNavigationOnClickListener { requireActivity().onBackPressed() } setOnMenuItemClickListener(this@PeakPlayerFragment) @@ -68,7 +80,7 @@ class PeakPlayerFragment : AbsPlayerFragment(R.layout.fragment_peak_player) { } override fun playerToolbar(): Toolbar { - return playerToolbar + return binding.playerToolbar } override fun onShow() { @@ -99,14 +111,14 @@ class PeakPlayerFragment : AbsPlayerFragment(R.layout.fragment_peak_player) { private fun updateSong() { val song = MusicPlayerRemote.currentSong - title.text = song.title - text.text = song.artistName + binding.title.text = song.title + binding.text.text = song.artistName if (PreferenceUtil.isSongInfo) { - songInfo.text = getSongInfo(song) - songInfo.show() + binding.songInfo.text = getSongInfo(song) + binding.songInfo.show() } else { - songInfo.hide() + binding.songInfo.hide() } } @@ -119,4 +131,9 @@ class PeakPlayerFragment : AbsPlayerFragment(R.layout.fragment_peak_player) { super.onPlayingMetaChanged() updateSong() } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/plain/PlainPlaybackControlsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/plain/PlainPlaybackControlsFragment.kt index eeea0970..2f3d45c3 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/plain/PlainPlaybackControlsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/plain/PlainPlaybackControlsFragment.kt @@ -28,6 +28,7 @@ import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentPlainControlsFragmentBinding import code.name.monkey.retromusic.extensions.applyColor import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.show @@ -40,15 +41,6 @@ import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import kotlinx.android.synthetic.main.fragment_adaptive_player_playback_controls.* -import kotlinx.android.synthetic.main.fragment_plain_controls_fragment.nextButton -import kotlinx.android.synthetic.main.fragment_plain_controls_fragment.playPauseButton -import kotlinx.android.synthetic.main.fragment_plain_controls_fragment.previousButton -import kotlinx.android.synthetic.main.fragment_plain_controls_fragment.progressSlider -import kotlinx.android.synthetic.main.fragment_plain_controls_fragment.repeatButton -import kotlinx.android.synthetic.main.fragment_plain_controls_fragment.shuffleButton -import kotlinx.android.synthetic.main.fragment_plain_controls_fragment.songCurrentProgress -import kotlinx.android.synthetic.main.fragment_plain_controls_fragment.songTotalTime /** * @author Hemanth S (h4h13). @@ -60,6 +52,8 @@ class PlainPlaybackControlsFragment : private var lastPlaybackControlsColor: Int = 0 private var lastDisabledPlaybackControlsColor: Int = 0 private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper + private var _binding: FragmentPlainControlsFragmentBinding? = null + private val binding get() = _binding!! override fun onPlayStateChanged() { updatePlayPauseDrawableState() @@ -87,10 +81,10 @@ class PlainPlaybackControlsFragment : private fun updateSong() { if (PreferenceUtil.isSongInfo) { - songInfo.text = getSongInfo(MusicPlayerRemote.currentSong) - songInfo.show() + binding.songInfo.text = getSongInfo(MusicPlayerRemote.currentSong) + binding.songInfo.show() } else { - songInfo.hide() + binding.songInfo.hide() } } @@ -111,20 +105,21 @@ class PlainPlaybackControlsFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentPlainControlsFragmentBinding.bind(view) setUpMusicControllers() - playPauseButton.setOnClickListener { + binding.playPauseButton.setOnClickListener { if (MusicPlayerRemote.isPlaying) { MusicPlayerRemote.pauseSong() } else { MusicPlayerRemote.resumePlaying() } - showBonceAnimation() + showBounceAnimation() } } private fun setUpPlayPauseFab() { - playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) + binding.playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) } private fun setUpMusicControllers() { @@ -137,13 +132,13 @@ class PlainPlaybackControlsFragment : private fun setUpPrevNext() { updatePrevNextColor() - nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } - previousButton.setOnClickListener { MusicPlayerRemote.back() } + binding.nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } + binding.previousButton.setOnClickListener { MusicPlayerRemote.back() } } private fun updatePrevNextColor() { - nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) - previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) } override fun setColor(color: MediaNotificationProcessor) { @@ -166,17 +161,17 @@ class PlainPlaybackControlsFragment : ThemeStore.accentColor(requireContext()) } volumeFragment?.setTintable(colorFinal) - progressSlider.applyColor(colorFinal) + binding.progressSlider.applyColor(colorFinal) TintHelper.setTintAuto( - playPauseButton, + binding.playPauseButton, MaterialValueHelper.getPrimaryTextColor( requireContext(), ColorUtil.isColorLight(colorFinal) ), false ) - TintHelper.setTintAuto(playPauseButton, colorFinal, true) + TintHelper.setTintAuto(binding.playPauseButton, colorFinal, true) updateRepeatState() updateShuffleState() @@ -184,16 +179,16 @@ class PlainPlaybackControlsFragment : } private fun setUpShuffleButton() { - shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } + binding.shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } } override fun updateShuffleState() { when (MusicPlayerRemote.shuffleMode) { - MusicService.SHUFFLE_MODE_SHUFFLE -> shuffleButton.setColorFilter( + MusicService.SHUFFLE_MODE_SHUFFLE -> binding.shuffleButton.setColorFilter( lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) - else -> shuffleButton.setColorFilter( + else -> binding.shuffleButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) @@ -201,31 +196,37 @@ class PlainPlaybackControlsFragment : } private fun setUpRepeatButton() { - repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } + binding.repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } } override fun updateRepeatState() { when (MusicPlayerRemote.repeatMode) { MusicService.REPEAT_MODE_NONE -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter( + binding.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.repeatButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) } MusicService.REPEAT_MODE_ALL -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } MusicService.REPEAT_MODE_THIS -> { - repeatButton.setImageResource(R.drawable.ic_repeat_one) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat_one) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } } } public override fun show() { - playPauseButton!!.animate() + binding.playPauseButton.animate() .scaleX(1f) .scaleY(1f) .rotation(360f) @@ -234,17 +235,15 @@ class PlainPlaybackControlsFragment : } public override fun hide() { - if (playPauseButton != null) { - playPauseButton!!.apply { - scaleX = 0f - scaleY = 0f - rotation = 0f - } + binding.playPauseButton.apply { + scaleX = 0f + scaleY = 0f + rotation = 0f } } - private fun showBonceAnimation() { - playPauseButton.apply { + private fun showBounceAnimation() { + binding.playPauseButton.apply { clearAnimation() scaleX = 0.9f scaleY = 0.9f @@ -268,14 +267,14 @@ class PlainPlaybackControlsFragment : private fun updatePlayPauseDrawableState() { if (MusicPlayerRemote.isPlaying) { - playPauseButton.setImageResource(R.drawable.ic_pause) + binding.playPauseButton.setImageResource(R.drawable.ic_pause) } else { - playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_32dp) + binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_32dp) } } override fun setUpProgressSlider() { - progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { + binding.progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) { MusicPlayerRemote.seekTo(progress) @@ -289,14 +288,19 @@ class PlainPlaybackControlsFragment : } override fun onUpdateProgressViews(progress: Int, total: Int) { - progressSlider.max = total + binding.progressSlider.max = total - val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) + val animator = ObjectAnimator.ofInt(binding.progressSlider, "progress", progress) animator.duration = SLIDER_ANIMATION_TIME animator.interpolator = LinearInterpolator() animator.start() - songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) - songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) + binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/plain/PlainPlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/plain/PlainPlayerFragment.kt index 8d26e678..220ef458 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/plain/PlainPlayerFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/plain/PlainPlayerFragment.kt @@ -20,22 +20,27 @@ import androidx.appcompat.widget.Toolbar import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentPlainPlayerBinding import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment +import code.name.monkey.retromusic.fragments.base.goToAlbum +import code.name.monkey.retromusic.fragments.base.goToArtist import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import kotlinx.android.synthetic.main.fragment_plain_player.* class PlainPlayerFragment : AbsPlayerFragment(R.layout.fragment_plain_player) { override fun playerToolbar(): Toolbar { - return playerToolbar + return binding.playerToolbar } private lateinit var plainPlaybackControlsFragment: PlainPlaybackControlsFragment private var lastColor: Int = 0 override val paletteColor: Int get() = lastColor + private var _binding: FragmentPlainPlayerBinding? = null + private val binding get() = _binding!! + override fun onPlayingMetaChanged() { super.onPlayingMetaChanged() @@ -44,8 +49,8 @@ class PlainPlayerFragment : AbsPlayerFragment(R.layout.fragment_plain_player) { private fun updateSong() { val song = MusicPlayerRemote.currentSong - title.text = song.title - text.text = song.artistName + binding.title.text = song.title + binding.text.text = song.artistName } override fun onServiceConnected() { @@ -54,7 +59,7 @@ class PlainPlayerFragment : AbsPlayerFragment(R.layout.fragment_plain_player) { } private fun setUpPlayerToolbar() { - playerToolbar.apply { + binding.playerToolbar.apply { inflateMenu(R.menu.menu_player) setNavigationOnClickListener { requireActivity().onBackPressed() } setOnMenuItemClickListener(this@PlainPlayerFragment) @@ -68,10 +73,17 @@ class PlainPlayerFragment : AbsPlayerFragment(R.layout.fragment_plain_player) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentPlainPlayerBinding.bind(view) setUpSubFragments() setUpPlayerToolbar() - title.isSelected = true - text.isSelected = true + binding.title.isSelected = true + binding.text.isSelected = true + binding.title.setOnClickListener { + goToAlbum(requireActivity()) + } + binding.text.setOnClickListener { + goToArtist(requireActivity()) + } } private fun setUpSubFragments() { @@ -104,7 +116,7 @@ class PlainPlayerFragment : AbsPlayerFragment(R.layout.fragment_plain_player) { lastColor = color.primaryTextColor libraryViewModel.updateColor(color.primaryTextColor) ToolbarContentTintHelper.colorizeToolbar( - playerToolbar, + binding.playerToolbar, ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal), requireActivity() ) @@ -120,4 +132,9 @@ class PlainPlayerFragment : AbsPlayerFragment(R.layout.fragment_plain_player) { updateIsFavorite() } } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/simple/SimplePlaybackControlsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/simple/SimplePlaybackControlsFragment.kt index 5a27715c..16aaaf71 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/simple/SimplePlaybackControlsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/simple/SimplePlaybackControlsFragment.kt @@ -24,9 +24,12 @@ import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentSimpleControlsFragmentBinding import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment +import code.name.monkey.retromusic.fragments.base.goToAlbum +import code.name.monkey.retromusic.fragments.base.goToArtist import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler @@ -34,7 +37,6 @@ import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import kotlinx.android.synthetic.main.fragment_simple_controls_fragment.* /** * @author Hemanth S (h4h13). @@ -43,6 +45,10 @@ import kotlinx.android.synthetic.main.fragment_simple_controls_fragment.* class SimplePlaybackControlsFragment : AbsPlayerControlsFragment(R.layout.fragment_simple_controls_fragment) { + private var _binding: FragmentSimpleControlsFragmentBinding? = null + private val binding get() = _binding!! + + private var lastPlaybackControlsColor: Int = 0 private var lastDisabledPlaybackControlsColor: Int = 0 private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper @@ -83,15 +89,25 @@ class SimplePlaybackControlsFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentSimpleControlsFragmentBinding.bind(view) setUpMusicControllers() - title.isSelected = true - playPauseButton.setOnClickListener { + binding.title.isSelected = true + binding.text.setOnClickListener { + goToArtist(requireActivity()) + } + binding.playPauseButton.setOnClickListener { if (MusicPlayerRemote.isPlaying) { MusicPlayerRemote.pauseSong() } else { MusicPlayerRemote.resumePlaying() } - showBonceAnimation(playPauseButton) + showBounceAnimation(binding.playPauseButton) + } + binding.title.setOnClickListener { + goToAlbum(requireActivity()) + } + binding.text.setOnClickListener { + goToArtist(requireActivity()) } } @@ -105,26 +121,26 @@ class SimplePlaybackControlsFragment : private fun setUpPrevNext() { updatePrevNextColor() - nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } - previousButton.setOnClickListener { MusicPlayerRemote.back() } + binding.nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } + binding.previousButton.setOnClickListener { MusicPlayerRemote.back() } } private fun updatePrevNextColor() { - nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) - previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) } private fun setUpShuffleButton() { - shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } + binding.shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } } override fun updateShuffleState() { when (MusicPlayerRemote.shuffleMode) { - MusicService.SHUFFLE_MODE_SHUFFLE -> shuffleButton.setColorFilter( + MusicService.SHUFFLE_MODE_SHUFFLE -> binding.shuffleButton.setColorFilter( lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) - else -> shuffleButton.setColorFilter( + else -> binding.shuffleButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) @@ -132,39 +148,45 @@ class SimplePlaybackControlsFragment : } private fun setUpRepeatButton() { - repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } + binding.repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } } override fun updateRepeatState() { when (MusicPlayerRemote.repeatMode) { MusicService.REPEAT_MODE_NONE -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter( + binding.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.repeatButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) } MusicService.REPEAT_MODE_ALL -> { - repeatButton.setImageResource(R.drawable.ic_repeat) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } MusicService.REPEAT_MODE_THIS -> { - repeatButton.setImageResource(R.drawable.ic_repeat_one) - repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.repeatButton.setImageResource(R.drawable.ic_repeat_one) + binding.repeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } } } private fun updateSong() { val song = MusicPlayerRemote.currentSong - title.text = song.title - text.text = song.artistName + binding.title.text = song.title + binding.text.text = song.artistName if (PreferenceUtil.isSongInfo) { - songInfo.text = getSongInfo(song) - songInfo.show() + binding.songInfo.text = getSongInfo(song) + binding.songInfo.show() } else { - songInfo.hide() + binding.songInfo.hide() } } @@ -174,7 +196,7 @@ class SimplePlaybackControlsFragment : } public override fun show() { - playPauseButton!!.animate() + binding.playPauseButton.animate() .scaleX(1f) .scaleY(1f) .rotation(360f) @@ -183,12 +205,10 @@ class SimplePlaybackControlsFragment : } public override fun hide() { - if (playPauseButton != null) { - playPauseButton!!.apply { - scaleX = 0f - scaleY = 0f - rotation = 0f - } + binding.playPauseButton.apply { + scaleX = 0f + scaleY = 0f + rotation = 0f } } @@ -196,7 +216,7 @@ class SimplePlaybackControlsFragment : } override fun onUpdateProgressViews(progress: Int, total: Int) { - songCurrentProgress!!.text = String.format( + binding.songCurrentProgress.text = String.format( "%s / %s", MusicUtil.getReadableDurationString(progress.toLong()), MusicUtil.getReadableDurationString(total.toLong()) @@ -226,15 +246,15 @@ class SimplePlaybackControlsFragment : volumeFragment?.setTintable(colorFinal) TintHelper.setTintAuto( - playPauseButton, + binding.playPauseButton, MaterialValueHelper.getPrimaryTextColor( requireContext(), ColorUtil.isColorLight(colorFinal) ), false ) - TintHelper.setTintAuto(playPauseButton, colorFinal, true) - text.setTextColor(colorFinal) + TintHelper.setTintAuto(binding.playPauseButton, colorFinal, true) + binding.text.setTextColor(colorFinal) updateRepeatState() updateShuffleState() @@ -242,14 +262,19 @@ class SimplePlaybackControlsFragment : } private fun setUpPlayPauseFab() { - playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) + binding.playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) } private fun updatePlayPauseDrawableState() { if (MusicPlayerRemote.isPlaying) { - playPauseButton.setImageResource(R.drawable.ic_pause) + binding.playPauseButton.setImageResource(R.drawable.ic_pause) } else { - playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_32dp) + binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_32dp) } } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/simple/SimplePlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/simple/SimplePlayerFragment.kt index 161cdb63..1a2b354d 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/simple/SimplePlayerFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/simple/SimplePlayerFragment.kt @@ -20,12 +20,12 @@ import androidx.appcompat.widget.Toolbar import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentSimplePlayerBinding import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import kotlinx.android.synthetic.main.fragment_simple_player.* /** * @author Hemanth S (h4h13). @@ -33,8 +33,11 @@ import kotlinx.android.synthetic.main.fragment_simple_player.* class SimplePlayerFragment : AbsPlayerFragment(R.layout.fragment_simple_player) { + private var _binding: FragmentSimplePlayerBinding? = null + private val binding get() = _binding!! + override fun playerToolbar(): Toolbar { - return playerToolbar + return binding.playerToolbar } private var lastColor: Int = 0 @@ -45,6 +48,7 @@ class SimplePlayerFragment : AbsPlayerFragment(R.layout.fragment_simple_player) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentSimplePlayerBinding.bind(view) setUpSubFragments() setUpPlayerToolbar() } @@ -78,7 +82,7 @@ class SimplePlayerFragment : AbsPlayerFragment(R.layout.fragment_simple_player) libraryViewModel.updateColor(color.backgroundColor) controlsFragment.setColor(color) ToolbarContentTintHelper.colorizeToolbar( - playerToolbar, + binding.playerToolbar, ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal), requireActivity() ) @@ -96,13 +100,18 @@ class SimplePlayerFragment : AbsPlayerFragment(R.layout.fragment_simple_player) } private fun setUpPlayerToolbar() { - playerToolbar.inflateMenu(R.menu.menu_player) - playerToolbar.setNavigationOnClickListener { requireActivity().onBackPressed() } - playerToolbar.setOnMenuItemClickListener(this) + binding.playerToolbar.inflateMenu(R.menu.menu_player) + binding.playerToolbar.setNavigationOnClickListener { requireActivity().onBackPressed() } + binding.playerToolbar.setOnMenuItemClickListener(this) ToolbarContentTintHelper.colorizeToolbar( - playerToolbar, + binding.playerToolbar, ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal), requireActivity() ) } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/tiny/TinyPlaybackControlsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/tiny/TinyPlaybackControlsFragment.kt index 04ab1204..3cfbc2f8 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/tiny/TinyPlaybackControlsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/tiny/TinyPlaybackControlsFragment.kt @@ -19,14 +19,16 @@ import android.os.Bundle import android.view.View import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentTinyControlsFragmentBinding import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import kotlinx.android.synthetic.main.fragment_tiny_controls_fragment.* class TinyPlaybackControlsFragment : AbsPlayerControlsFragment(R.layout.fragment_tiny_controls_fragment) { + private var _binding: FragmentTinyControlsFragmentBinding? = null + private val binding get() = _binding!! override fun show() { } @@ -53,6 +55,7 @@ class TinyPlaybackControlsFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentTinyControlsFragmentBinding.bind(view) setUpMusicControllers() } @@ -63,20 +66,20 @@ class TinyPlaybackControlsFragment : } private fun setUpShuffleButton() { - playerShuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } + binding.playerShuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } } private fun setUpRepeatButton() { - playerRepeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } + binding.playerRepeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } } override fun updateShuffleState() { when (MusicPlayerRemote.shuffleMode) { - MusicService.SHUFFLE_MODE_SHUFFLE -> playerShuffleButton.setColorFilter( + MusicService.SHUFFLE_MODE_SHUFFLE -> binding.playerShuffleButton.setColorFilter( lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) - else -> playerShuffleButton.setColorFilter( + else -> binding.playerShuffleButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) @@ -86,19 +89,25 @@ class TinyPlaybackControlsFragment : override fun updateRepeatState() { when (MusicPlayerRemote.repeatMode) { MusicService.REPEAT_MODE_NONE -> { - playerRepeatButton.setImageResource(R.drawable.ic_repeat) - playerRepeatButton.setColorFilter( + binding.playerRepeatButton.setImageResource(R.drawable.ic_repeat) + binding.playerRepeatButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) } MusicService.REPEAT_MODE_ALL -> { - playerRepeatButton.setImageResource(R.drawable.ic_repeat) - playerRepeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.playerRepeatButton.setImageResource(R.drawable.ic_repeat) + binding.playerRepeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } MusicService.REPEAT_MODE_THIS -> { - playerRepeatButton.setImageResource(R.drawable.ic_repeat_one) - playerRepeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + binding.playerRepeatButton.setImageResource(R.drawable.ic_repeat_one) + binding.playerRepeatButton.setColorFilter( + lastPlaybackControlsColor, + PorterDuff.Mode.SRC_IN + ) } } } @@ -115,4 +124,9 @@ class TinyPlaybackControlsFragment : override fun onShuffleModeChanged() { updateShuffleState() } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/tiny/TinyPlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/tiny/TinyPlayerFragment.kt index 9f4833c2..630efbce 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/tiny/TinyPlayerFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/tiny/TinyPlayerFragment.kt @@ -16,17 +16,22 @@ package code.name.monkey.retromusic.fragments.player.tiny import android.animation.AnimatorSet import android.animation.ObjectAnimator -import android.os.Bundle -import android.os.Handler +import android.annotation.SuppressLint +import android.content.Context +import android.os.* +import android.view.GestureDetector +import android.view.MotionEvent import android.view.View import android.view.animation.LinearInterpolator import androidx.appcompat.widget.Toolbar import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentTinyPlayerBinding import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.show -import code.name.monkey.retromusic.fragments.MiniPlayerFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment +import code.name.monkey.retromusic.fragments.base.goToAlbum +import code.name.monkey.retromusic.fragments.base.goToArtist import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper @@ -36,15 +41,20 @@ import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.ViewUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import kotlinx.android.synthetic.main.fragment_tiny_player.* +import kotlin.math.abs class TinyPlayerFragment : AbsPlayerFragment(R.layout.fragment_tiny_player), MusicProgressViewUpdateHelper.Callback { + private var _binding: FragmentTinyPlayerBinding? = null + private val binding get() = _binding!! + private var lastColor: Int = 0 private var toolbarColor: Int = 0 + private var isDragEnabled = false + lateinit var animator: ObjectAnimator override fun playerToolbar(): Toolbar { - return playerToolbar + return binding.playerToolbar } override fun onShow() { @@ -70,21 +80,22 @@ class TinyPlayerFragment : AbsPlayerFragment(R.layout.fragment_tiny_player), toolbarColor = color.secondaryTextColor controlsFragment.setColor(color) - title.setTextColor(color.primaryTextColor) - playerSongTotalTime.setTextColor(color.primaryTextColor) - text.setTextColor(color.secondaryTextColor) - songInfo.setTextColor(color.secondaryTextColor) - ViewUtil.setProgressDrawable(progressBar, color.backgroundColor) + binding.title.setTextColor(color.primaryTextColor) + binding.playerSongTotalTime.setTextColor(color.primaryTextColor) + binding.text.setTextColor(color.secondaryTextColor) + binding.songInfo.setTextColor(color.secondaryTextColor) + ViewUtil.setProgressDrawable(binding.progressBar, color.backgroundColor) - Handler().post { + Handler(Looper.myLooper()!!).post { ToolbarContentTintHelper.colorizeToolbar( - playerToolbar, + binding.playerToolbar, color.secondaryTextColor, requireActivity() ) } } + override fun onFavoriteToggled() { toggleFavorite(MusicPlayerRemote.currentSong) } @@ -109,25 +120,33 @@ class TinyPlayerFragment : AbsPlayerFragment(R.layout.fragment_tiny_player), private fun updateSong() { val song = MusicPlayerRemote.currentSong - title.text = song.title - text.text = String.format("%s \nby - %s", song.albumName, song.artistName) + binding.title.text = song.title + binding.text.text = String.format("%s \nby - %s", song.albumName, song.artistName) if (PreferenceUtil.isSongInfo) { - songInfo.text = getSongInfo(song) - songInfo.show() + binding.songInfo.text = getSongInfo(song) + binding.songInfo.show() } else { - songInfo.hide() + binding.songInfo.hide() } } + @SuppressLint("ClickableViewAccessibility") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - title.isSelected = true - progressBar.setOnClickListener(PlayPauseButtonOnClickHandler()) - progressBar.setOnTouchListener(MiniPlayerFragment.FlingPlayBackController(requireContext())) + _binding = FragmentTinyPlayerBinding.bind(view) + binding.title.isSelected = true + binding.progressBar.setOnClickListener(PlayPauseButtonOnClickHandler()) + binding.progressBar.setOnTouchListener(ProgressHelper(requireContext())) setUpPlayerToolbar() setUpSubFragments() + binding.title.setOnClickListener { + goToAlbum(requireActivity()) + } + binding.text.setOnClickListener { + goToArtist(requireActivity()) + } } private fun setUpSubFragments() { @@ -139,7 +158,7 @@ class TinyPlayerFragment : AbsPlayerFragment(R.layout.fragment_tiny_player), } private fun setUpPlayerToolbar() { - playerToolbar.apply { + binding.playerToolbar.apply { inflateMenu(R.menu.menu_player) setNavigationOnClickListener { requireActivity().onBackPressed() } setOnMenuItemClickListener(this@TinyPlayerFragment) @@ -164,20 +183,113 @@ class TinyPlayerFragment : AbsPlayerFragment(R.layout.fragment_tiny_player), } override fun onUpdateProgressViews(progress: Int, total: Int) { - progressBar.max = total + binding.progressBar.max = total - val animator = ObjectAnimator.ofInt(progressBar, "progress", progress) + if (isDragEnabled) { + binding.progressBar.progress = progress + } else { + animator = ObjectAnimator.ofInt(binding.progressBar, "progress", progress) - val animatorSet = AnimatorSet() - animatorSet.playSequentially(animator) + val animatorSet = AnimatorSet() + animatorSet.playSequentially(animator) - animatorSet.duration = 1500 - animatorSet.interpolator = LinearInterpolator() - animatorSet.start() - - playerSongTotalTime.text = String.format( + animatorSet.duration = 1500 + animatorSet.interpolator = LinearInterpolator() + animatorSet.start() + } + binding.playerSongTotalTime.text = String.format( "%s/%s", MusicUtil.getReadableDurationString(total.toLong()), MusicUtil.getReadableDurationString(progress.toLong()) ) } + + inner class ProgressHelper(context: Context) : View.OnTouchListener { + private var initialY: Int = 0 + private var initialProgress = 0 + private var progress: Int = 0 + private val displayHeight = resources.displayMetrics.heightPixels + private var gestureDetector: GestureDetector + + init { + gestureDetector = GestureDetector(context, object : + GestureDetector.SimpleOnGestureListener() { + + override fun onLongPress(e: MotionEvent?) { + if (abs(e!!.y - initialY) <= 2) { + vibrate() + isDragEnabled = true + binding.progressBar.parent.requestDisallowInterceptTouchEvent(true) + animator.pause() + } + super.onLongPress(e) + } + + override fun onFling( + e1: MotionEvent, + e2: MotionEvent, + velocityX: Float, + velocityY: Float + ): Boolean { + if (abs(velocityX) > abs(velocityY)) { + if (velocityX < 0) { + MusicPlayerRemote.playNextSong() + return true + } else if (velocityX > 0) { + MusicPlayerRemote.playPreviousSong() + return true + } + } + return false + } + }) + } + + @SuppressLint("ClickableViewAccessibility") + override fun onTouch(v: View, event: MotionEvent): Boolean { + when (event.actionMasked) { + MotionEvent.ACTION_DOWN -> { + initialProgress = MusicPlayerRemote.songProgressMillis + initialY = event.y.toInt() + progressViewUpdateHelper.stop() + } + MotionEvent.ACTION_UP, + MotionEvent.ACTION_CANCEL -> { + progressViewUpdateHelper.start() + if (isDragEnabled) { + MusicPlayerRemote.seekTo(progress) + isDragEnabled = false + return true + } + } + MotionEvent.ACTION_MOVE -> { + if (isDragEnabled) { + val diffY = (initialY - event.y).toInt() + progress = + initialProgress + diffY * (binding.progressBar.max / displayHeight) // Multiplier + if (progress > 0 && progress < binding.progressBar.max) { + onUpdateProgressViews( + progress, + MusicPlayerRemote.songDurationMillis + ) + } + } + } + } + return gestureDetector.onTouchEvent(event) + } + + private fun vibrate() { + val v = requireContext().getSystemService(Context.VIBRATOR_SERVICE) as Vibrator? + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + v!!.vibrate(VibrationEffect.createOneShot(10, VibrationEffect.DEFAULT_AMPLITUDE)) + } else { + v!!.vibrate(10) + } + } + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistDetailsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistDetailsFragment.kt index 38c149b5..320f9a88 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistDetailsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistDetailsFragment.kt @@ -1,77 +1,101 @@ package code.name.monkey.retromusic.fragments.playlists -import android.graphics.Color import android.os.Bundle +import android.util.Log import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View +import androidx.activity.addCallback import androidx.core.view.ViewCompat +import androidx.core.view.doOnPreDraw import androidx.core.view.isVisible import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.retromusic.R -import code.name.monkey.retromusic.adapter.song.ShuffleButtonSongAdapter +import code.name.monkey.retromusic.adapter.song.OrderablePlaylistSongAdapter +import code.name.monkey.retromusic.databinding.FragmentPlaylistDetailBinding import code.name.monkey.retromusic.db.PlaylistWithSongs import code.name.monkey.retromusic.db.toSongs import code.name.monkey.retromusic.extensions.dipToPix -import code.name.monkey.retromusic.extensions.resolveColor +import code.name.monkey.retromusic.extensions.surfaceColor import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper +import code.name.monkey.retromusic.interfaces.ICabHolder import code.name.monkey.retromusic.model.Song -import com.google.android.material.transition.MaterialArcMotion -import com.google.android.material.transition.MaterialContainerTransform -import kotlinx.android.synthetic.main.fragment_playlist_detail.* +import code.name.monkey.retromusic.util.RetroColorUtil +import com.afollestad.materialcab.MaterialCab +import com.google.android.material.transition.MaterialSharedAxis +import com.h6ah4i.android.widget.advrecyclerview.animator.DraggableItemAnimator +import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator +import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf -class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playlist_detail) { + +class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playlist_detail), + ICabHolder { private val arguments by navArgs() private val viewModel by viewModel { parametersOf(arguments.extraPlaylist) } - private lateinit var playlist: PlaylistWithSongs - private lateinit var playlistSongAdapter: ShuffleButtonSongAdapter + private var _binding: FragmentPlaylistDetailBinding? = null + private val binding get() = _binding!! - 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()) - } - } + + private lateinit var playlist: PlaylistWithSongs + private lateinit var playlistSongAdapter: OrderablePlaylistSongAdapter override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + _binding = FragmentPlaylistDetailBinding.bind(view) + enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true).addTarget(view) + returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) setHasOptionsMenu(true) - mainActivity.setBottomBarVisibility(false) mainActivity.addMusicServiceEventListener(viewModel) - mainActivity.setSupportActionBar(toolbar) - ViewCompat.setTransitionName(container, "playlist") + mainActivity.setSupportActionBar(binding.toolbar) + ViewCompat.setTransitionName(binding.container, "playlist") playlist = arguments.extraPlaylist - toolbar.title = playlist.playlistEntity.playlistName + binding.toolbar.title = playlist.playlistEntity.playlistName setUpRecyclerView() viewModel.getSongs().observe(viewLifecycleOwner, { songs(it.toSongs()) }) + postponeEnterTransition() + requireView().doOnPreDraw { startPostponedEnterTransition() } + requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { + if (!handleBackPress()) { + remove() + requireActivity().onBackPressed() + } + } } private fun setUpRecyclerView() { - playlistSongAdapter = ShuffleButtonSongAdapter( + playlistSongAdapter = OrderablePlaylistSongAdapter( + playlist.playlistEntity, requireActivity(), ArrayList(), R.layout.item_list, - null, + this ) - recyclerView.apply { + + val dragDropManager = RecyclerViewDragDropManager() + + val wrappedAdapter: RecyclerView.Adapter<*> = + dragDropManager.createWrappedAdapter(playlistSongAdapter) + + + val animator: GeneralItemAnimator = DraggableItemAnimator() + binding.recyclerView.itemAnimator = animator + + dragDropManager.attachRecyclerView(binding.recyclerView) + + binding.recyclerView.apply { layoutManager = LinearLayoutManager(requireContext()) - adapter = playlistSongAdapter + binding.recyclerView.adapter = wrappedAdapter } playlistSongAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { @@ -93,32 +117,70 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli private fun checkForPadding() { val height = dipToPix(52f) - recyclerView.setPadding(0, 0, 0, height.toInt()) + binding.recyclerView.setPadding(0, 0, 0, height.toInt()) } private fun checkIsEmpty() { checkForPadding() - empty.isVisible = playlistSongAdapter.itemCount == 0 - emptyText.isVisible = playlistSongAdapter.itemCount == 0 + binding.empty.isVisible = playlistSongAdapter.itemCount == 0 + binding.emptyText.isVisible = playlistSongAdapter.itemCount == 0 } override fun onDestroy() { - recyclerView?.itemAnimator = null - recyclerView?.adapter = null super.onDestroy() + _binding = null + } + + override fun onPause() { + playlistSongAdapter.saveSongs(playlist.playlistEntity) + super.onPause() } private fun showEmptyView() { - empty.visibility = View.VISIBLE - emptyText.visibility = View.VISIBLE + binding.empty.visibility = View.VISIBLE + binding.emptyText.visibility = View.VISIBLE } fun songs(songs: List) { - progressIndicator.hide() + binding.progressIndicator.hide() if (songs.isNotEmpty()) { + Log.i("Updated", songs[0].title) playlistSongAdapter.swapDataSet(songs) } else { showEmptyView() } } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + private fun handleBackPress(): Boolean { + cab?.let { + if (it.isActive) { + it.finish() + return true + } + } + return false + } + + private var cab: MaterialCab? = null + + override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab { + cab?.let { + println("Cab") + if (it.isActive) { + it.finish() + } + } + cab = MaterialCab(mainActivity, R.id.cab_stub) + .setMenu(menuRes) + .setCloseDrawableRes(R.drawable.ic_close) + .setBackgroundColor(RetroColorUtil.shiftBackgroundColorForLightText(surfaceColor())) + .start(callback) + return cab as MaterialCab + } + } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistDetailsViewModel.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistDetailsViewModel.kt index 3f6f874e..3416caa2 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistDetailsViewModel.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistDetailsViewModel.kt @@ -41,4 +41,5 @@ class PlaylistDetailsViewModel( override fun onPlayStateChanged() {} override fun onRepeatModeChanged() {} override fun onShuffleModeChanged() {} + override fun onFavoriteStateChanged() {} } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistsFragment.kt index f99a43ce..9a4880fe 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistsFragment.kt @@ -15,17 +15,11 @@ package code.name.monkey.retromusic.fragments.playlists 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 android.view.* import androidx.core.os.bundleOf import androidx.core.view.MenuCompat -import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.GridLayoutManager -import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.retromusic.EXTRA_PLAYLIST import code.name.monkey.retromusic.R import code.name.monkey.retromusic.adapter.playlist.PlaylistAdapter @@ -35,12 +29,13 @@ import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewCustomGridSizeF import code.name.monkey.retromusic.helper.SortOrder.PlaylistSortOrder import code.name.monkey.retromusic.interfaces.IPlaylistClickListener import code.name.monkey.retromusic.util.PreferenceUtil -import com.google.android.material.transition.MaterialElevationScale -import kotlinx.android.synthetic.main.fragment_library.* +import com.google.android.gms.cast.framework.CastButtonFactory +import com.google.android.material.transition.MaterialSharedAxis class PlaylistsFragment : AbsRecyclerViewCustomGridSizeFragment(), IPlaylistClickListener { + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) libraryViewModel.getPlaylists().observe(viewLifecycleOwner, { @@ -51,6 +46,8 @@ class PlaylistsFragment : }) } + override val titleRes: Int + get() = R.string.playlists override val emptyMessage: Int get() = R.string.no_playlists @@ -68,11 +65,6 @@ class PlaylistsFragment : ) } - override fun onPrepareOptionsMenu(menu: Menu) { - super.onPrepareOptionsMenu(menu) - ToolbarContentTintHelper.handleOnPrepareOptionsMenu(requireActivity(), toolbar) - } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { super.onCreateOptionsMenu(menu, inflater) menu.removeItem(R.id.action_grid_size) @@ -81,7 +73,9 @@ class PlaylistsFragment : menu.add(0, R.id.action_import_playlist, 0, R.string.import_playlist) menu.findItem(R.id.action_settings).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) setUpSortOrderMenu(menu.findItem(R.id.action_sort_order).subMenu) - MenuCompat.setGroupDividerEnabled(menu, true); + MenuCompat.setGroupDividerEnabled(menu, true) + //Setting up cast button + CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.action_cast) } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -160,7 +154,7 @@ class PlaylistsFragment : } override fun loadGridSize(): Int { - return 1 + return 2 } override fun saveGridSize(gridColumns: Int) { @@ -168,7 +162,7 @@ class PlaylistsFragment : } override fun loadGridSizeLand(): Int { - return 2 + return 4 } override fun saveGridSizeLand(gridColumns: Int) { @@ -176,7 +170,7 @@ class PlaylistsFragment : } override fun loadLayoutRes(): Int { - return R.layout.item_list + return R.layout.item_card } override fun saveLayoutRes(layoutRes: Int) { @@ -184,17 +178,13 @@ class PlaylistsFragment : } override fun onPlaylistClick(playlistWithSongs: PlaylistWithSongs, view: View) { - exitTransition = MaterialElevationScale(false).apply { - duration = 300L - } - reenterTransition = MaterialElevationScale(true).apply { - duration = 300L - } + exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true).addTarget(requireView()) + reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) findNavController().navigate( R.id.playlistDetailsFragment, bundleOf(EXTRA_PLAYLIST to playlistWithSongs), null, - FragmentNavigatorExtras(view to "playlist") + null ) } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/queue/PlayingQueueFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/queue/PlayingQueueFragment.kt index 0af0c72c..c8a218c7 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/queue/PlayingQueueFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/queue/PlayingQueueFragment.kt @@ -28,7 +28,6 @@ import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropM import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils -import kotlinx.android.synthetic.main.activity_playing_queue.* /** * Created by hemanths on 2019-12-08. @@ -39,6 +38,8 @@ class PlayingQueueFragment : AbsRecyclerViewFragment) { @@ -100,21 +129,21 @@ class SearchFragment : AbsMainActivityFragment(R.layout.fragment_search), TextWa searchAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { override fun onChanged() { super.onChanged() - empty.isVisible = searchAdapter.itemCount < 1 + binding.empty.isVisible = searchAdapter.itemCount < 1 val height = dipToPix(52f) - recyclerView.setPadding(0, 0, 0, height.toInt()) + binding.recyclerView.setPadding(0, 0, 0, height.toInt()) } }) - recyclerView.apply { + binding.recyclerView.apply { layoutManager = LinearLayoutManager(requireContext()) adapter = searchAdapter addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) if (dy > 0) { - keyboardPopup.shrink() + binding.keyboardPopup.shrink() } else if (dy < 0) { - keyboardPopup.extend() + binding.keyboardPopup.extend() } } }) @@ -122,7 +151,7 @@ class SearchFragment : AbsMainActivityFragment(R.layout.fragment_search), TextWa } override fun afterTextChanged(newText: Editable?) { - search(newText.toString()) + if (!newText.isNullOrEmpty()) search(newText.toString()) } override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { @@ -133,10 +162,17 @@ class SearchFragment : AbsMainActivityFragment(R.layout.fragment_search), TextWa private fun search(query: String) { this.query = query - TransitionManager.beginDelayedTransition(appBarLayout) - voiceSearch.isGone = query.isNotEmpty() - clearText.isVisible = query.isNotEmpty() - libraryViewModel.search(query) + TransitionManager.beginDelayedTransition(binding.appBarLayout) + binding.voiceSearch.isGone = query.isNotEmpty() + binding.clearText.isVisible = query.isNotEmpty() + val filters = getFilters() + libraryViewModel.search(query, filters) + } + + private fun getFilters(): List { + return binding.searchFilterGroup.children.toList().map { + (it as Chip).isChecked + } } private fun startMicSearch() { @@ -161,6 +197,17 @@ class SearchFragment : AbsMainActivityFragment(R.layout.fragment_search), TextWa override fun onDestroyView() { hideKeyboard(view) super.onDestroyView() + _binding = null + } + + override fun onPause() { + super.onPause() + hideKeyboard(view) + } + + override fun onResume() { + super.onResume() + mainActivity.setBottomBarVisibility(false) } private fun hideKeyboard(view: View?) { @@ -170,6 +217,27 @@ class SearchFragment : AbsMainActivityFragment(R.layout.fragment_search), TextWa imm.hideSoftInputFromWindow(view.windowToken, 0) } } + + override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) { + val checkedChip = (buttonView as Chip) + checkedChip.isCloseIconVisible = isChecked + if (isChecked) { + val color = ThemeStore.textColorPrimaryInverse(requireContext()) + checkedChip.apply { + setTextColor(color) + chipIconTint = ColorStateList.valueOf(color) + chipStrokeWidth = 0F + } + } else { + val color = ThemeStore.textColorPrimary(requireContext()) + checkedChip.apply { + setTextColor(color) + chipIconTint = ColorStateList.valueOf(color) + chipStrokeWidth = 2F + } + } + search(binding.searchView.text.toString()) + } } fun TextInputEditText.clearText() { diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/settings/AbsSettingsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/settings/AbsSettingsFragment.kt index 19190713..5ef8ce7e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/settings/AbsSettingsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/settings/AbsSettingsFragment.kt @@ -87,6 +87,10 @@ abstract class AbsSettingsFragment : ATEPreferenceFragmentCompat() { val fragment = BlacklistPreferenceDialog.newInstance() fragment.show(childFragmentManager, preference.key) } + is DurationPreference -> { + val fragment = DurationPreferenceDialog.newInstance() + fragment.show(childFragmentManager, preference.key) + } else -> super.onDisplayPreferenceDialog(preference) } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/settings/MainSettingsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/settings/MainSettingsFragment.kt index 45160814..30b39fa5 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/settings/MainSettingsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/settings/MainSettingsFragment.kt @@ -24,12 +24,17 @@ import androidx.navigation.fragment.findNavController import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.retromusic.App import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.FragmentMainSettingsBinding import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.util.NavigationUtil -import kotlinx.android.synthetic.main.fragment_main_settings.* class MainSettingsFragment : Fragment(), View.OnClickListener { + + private var _binding: FragmentMainSettingsBinding? = null + private val binding get() = _binding!! + + override fun onClick(view: View) { when (view.id) { R.id.generalSettings -> findNavController().navigate(R.id.action_mainSettingsFragment_to_themeSettingsFragment) @@ -47,34 +52,40 @@ class MainSettingsFragment : Fragment(), View.OnClickListener { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { - return inflater.inflate(R.layout.fragment_main_settings, container, false) + ): View { + _binding = FragmentMainSettingsBinding.inflate(inflater, container, false) + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - generalSettings.setOnClickListener(this) - audioSettings.setOnClickListener(this) - nowPlayingSettings.setOnClickListener(this) - personalizeSettings.setOnClickListener(this) - imageSettings.setOnClickListener(this) - notificationSettings.setOnClickListener(this) - otherSettings.setOnClickListener(this) - aboutSettings.setOnClickListener(this) + binding.generalSettings.setOnClickListener(this) + binding.audioSettings.setOnClickListener(this) + binding.nowPlayingSettings.setOnClickListener(this) + binding.personalizeSettings.setOnClickListener(this) + binding.imageSettings.setOnClickListener(this) + binding.notificationSettings.setOnClickListener(this) + binding.otherSettings.setOnClickListener(this) + binding.aboutSettings.setOnClickListener(this) - buyProContainer.apply { + binding.buyProContainer.apply { if (App.isProVersion()) hide() else show() setOnClickListener { NavigationUtil.goToProVersion(requireContext()) } } - buyPremium.setOnClickListener { + binding.buyPremium.setOnClickListener { NavigationUtil.goToProVersion(requireContext()) } ThemeStore.accentColor(requireContext()).let { - buyPremium.setTextColor(it) - diamondIcon.imageTintList = ColorStateList.valueOf(it) + binding.buyPremium.setTextColor(it) + binding.diamondIcon.imageTintList = ColorStateList.valueOf(it) } } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/settings/OtherSettingsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/settings/OtherSettingsFragment.kt index 319fcda8..f7fb48fe 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/settings/OtherSettingsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/settings/OtherSettingsFragment.kt @@ -23,7 +23,10 @@ import code.name.monkey.retromusic.LAST_ADDED_CUTOFF import code.name.monkey.retromusic.R import code.name.monkey.retromusic.fragments.LibraryViewModel import code.name.monkey.retromusic.fragments.ReloadType.HomeSections +import com.google.android.play.core.splitinstall.SplitInstallManagerFactory +import com.google.android.play.core.splitinstall.SplitInstallRequest import org.koin.androidx.viewmodel.ext.android.sharedViewModel +import java.util.* /** * @author Hemanth S (h4h13). @@ -35,6 +38,7 @@ class OtherSettingsFragment : AbsSettingsFragment() { override fun invalidateSettings() { val languagePreference: ATEListPreference? = findPreference(LANGUAGE_NAME) languagePreference?.setOnPreferenceChangeListener { _, _ -> + println("Invalidated") requireActivity().recreate() return@setOnPreferenceChangeListener true } @@ -55,6 +59,21 @@ class OtherSettingsFragment : AbsSettingsFragment() { val languagePreference: Preference? = findPreference(LANGUAGE_NAME) languagePreference?.setOnPreferenceChangeListener { prefs, newValue -> setSummary(prefs, newValue) + val code = newValue.toString() + val manager = SplitInstallManagerFactory.create(requireContext()) + if (code != "auto") { + // Try to download language resources + val request = + SplitInstallRequest.newBuilder().addLanguage(Locale.forLanguageTag(code)) + .build() + manager.startInstall(request) + // Recreate the activity on download complete + .addOnCompleteListener { + activity?.recreate() + } + } else { + requireActivity().recreate() + } true } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/settings/ThemeSettingsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/settings/ThemeSettingsFragment.kt index b138d299..befbca95 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/settings/ThemeSettingsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/settings/ThemeSettingsFragment.kt @@ -18,6 +18,8 @@ import android.os.Build import android.os.Bundle import androidx.preference.Preference import androidx.preference.TwoStatePreference +import code.name.monkey.appthemehelper.ACCENT_COLORS +import code.name.monkey.appthemehelper.ACCENT_COLORS_SUB import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.common.prefs.supportv7.ATEColorPreference import code.name.monkey.appthemehelper.common.prefs.supportv7.ATESwitchPreference @@ -26,7 +28,8 @@ import code.name.monkey.appthemehelper.util.VersionUtils import code.name.monkey.retromusic.* import code.name.monkey.retromusic.appshortcuts.DynamicShortcutManager import code.name.monkey.retromusic.util.PreferenceUtil -import com.afollestad.materialdialogs.color.ColorChooserDialog +import com.afollestad.materialdialogs.MaterialDialog +import com.afollestad.materialdialogs.color.colorChooser /** * @author Hemanth S (h4h13). @@ -55,12 +58,19 @@ class ThemeSettingsFragment : AbsSettingsFragment() { val accentColor = ThemeStore.accentColor(requireContext()) accentColorPref?.setColor(accentColor, ColorUtil.darkenColor(accentColor)) accentColorPref?.setOnPreferenceClickListener { - ColorChooserDialog.Builder(requireContext(), R.string.accent_color) - .accentMode(true) - .allowUserColorInput(true) - .allowUserColorInputAlpha(false) - .preselect(accentColor) - .show(requireActivity()) + MaterialDialog(requireContext()).show { + colorChooser( + initialSelection = accentColor, + showAlphaSelector = false, + colors = ACCENT_COLORS, + subColors = ACCENT_COLORS_SUB, allowCustomArgb = true + ) { _, color -> + ThemeStore.editTheme(requireContext()).accentColor(color).commit() + if (VersionUtils.hasNougatMR()) + DynamicShortcutManager(requireContext()).updateDynamicShortcuts() + requireActivity().recreate() + } + } return@setOnPreferenceClickListener true } val blackTheme: ATESwitchPreference? = findPreference(BLACK_THEME) diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/songs/SongsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/songs/SongsFragment.kt index c9f79141..022b4999 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/songs/SongsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/songs/SongsFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Hemanth Savarla. + * Cop()yright (c) 2020 Hemanth Savarla. * * Licensed under the GNU General Public License v3 * @@ -16,8 +16,8 @@ package code.name.monkey.retromusic.fragments.songs import android.os.Bundle import android.view.* +import androidx.activity.addCallback import androidx.annotation.LayoutRes -import androidx.lifecycle.Observer import androidx.recyclerview.widget.GridLayoutManager import code.name.monkey.retromusic.R import code.name.monkey.retromusic.adapter.song.ShuffleButtonSongAdapter @@ -31,6 +31,7 @@ import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.RetroColorUtil import code.name.monkey.retromusic.util.RetroUtil import com.afollestad.materialcab.MaterialCab +import com.google.android.gms.cast.framework.CastButtonFactory class SongsFragment : AbsRecyclerViewCustomGridSizeFragment(), ICabHolder { @@ -42,13 +43,29 @@ class SongsFragment : AbsRecyclerViewCustomGridSizeFragment SongSortOrder.SONG_A_Z R.id.action_song_sort_order_desc -> SongSortOrder.SONG_Z_A R.id.action_song_sort_order_artist -> SongSortOrder.SONG_ARTIST + R.id.action_song_sort_order_album_artist -> SongSortOrder.SONG_ALBUM_ARTIST R.id.action_song_sort_order_album -> SongSortOrder.SONG_ALBUM R.id.action_song_sort_order_year -> SongSortOrder.SONG_YEAR R.id.action_song_sort_order_date -> SongSortOrder.SONG_DATE @@ -306,6 +333,11 @@ class SongsFragment : AbsRecyclerViewCustomGridSizeFragment build() { - //noinspection unchecked - return createBaseRequest(requestManager, song, ignoreMediaStore) - .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) - .error(DEFAULT_ERROR_IMAGE) - .animate(DEFAULT_ANIMATION) - .signature(createSignature(song)); - } - } - - public static class BitmapBuilder { - private final Builder builder; - - BitmapBuilder(Builder builder) { - this.builder = builder; - } - - public BitmapRequestBuilder build() { - //noinspection unchecked - return createBaseRequest(builder.requestManager, builder.song, builder.ignoreMediaStore) - .asBitmap() - .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) - .error(DEFAULT_ERROR_IMAGE) - .animate(DEFAULT_ANIMATION) - .dontTransform() - .signature(createSignature(builder.song)); - } - } - - public static class PaletteBuilder { - private final Context context; - private final Builder builder; - - PaletteBuilder(Builder builder, Context context) { - this.builder = builder; - this.context = context; - } - - public BitmapRequestBuilder build() { - - //noinspection unchecked - return createBaseRequest(builder.requestManager, builder.song, builder.ignoreMediaStore) - .asBitmap() - .transcode(new BitmapPaletteTranscoder(context), BitmapPaletteWrapper.class) - .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) - .error(DEFAULT_ERROR_IMAGE) - .animate(DEFAULT_ANIMATION) - .signature(createSignature(builder.song)); - } - } -} diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/ArtistGlideRequest.java b/app/src/main/java/code/name/monkey/retromusic/glide/ArtistGlideRequest.java deleted file mode 100644 index b04854fa..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/glide/ArtistGlideRequest.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (c) 2019 Hemanth Savarala. - * - * 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.glide; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; -import androidx.annotation.NonNull; -import androidx.core.content.ContextCompat; -import code.name.monkey.appthemehelper.ThemeStore; -import code.name.monkey.appthemehelper.util.TintHelper; -import code.name.monkey.retromusic.App; -import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.glide.artistimage.ArtistImage; -import code.name.monkey.retromusic.glide.palette.BitmapPaletteTranscoder; -import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper; -import code.name.monkey.retromusic.model.Artist; -import code.name.monkey.retromusic.util.ArtistSignatureUtil; -import code.name.monkey.retromusic.util.CustomArtistImageUtil; -import com.bumptech.glide.BitmapRequestBuilder; -import com.bumptech.glide.DrawableRequestBuilder; -import com.bumptech.glide.DrawableTypeRequest; -import com.bumptech.glide.Priority; -import com.bumptech.glide.RequestManager; -import com.bumptech.glide.load.Key; -import com.bumptech.glide.load.engine.DiskCacheStrategy; -import com.bumptech.glide.load.resource.drawable.GlideDrawable; -import com.bumptech.glide.request.target.Target; - -public class ArtistGlideRequest { - private static final int DEFAULT_ANIMATION = android.R.anim.fade_in; - - private static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.SOURCE; - - private static final int DEFAULT_ERROR_IMAGE = R.drawable.default_artist_art; - - @NonNull - private static Key createSignature(@NonNull Artist artist) { - return ArtistSignatureUtil.getInstance(App.Companion.getContext()) - .getArtistSignature(artist.getName()); - } - - @NonNull - private static DrawableTypeRequest createBaseRequest( - @NonNull RequestManager requestManager, - @NonNull Artist artist, - boolean noCustomImage, - boolean forceDownload) { - boolean hasCustomImage = - CustomArtistImageUtil.Companion.getInstance(App.Companion.getContext()) - .hasCustomArtistImage(artist); - if (noCustomImage || !hasCustomImage) { - return requestManager.load(new ArtistImage(artist)); - } else { - return requestManager.load(CustomArtistImageUtil.getFile(artist)); - } - } - - public static class Builder { - final Artist artist; - final RequestManager requestManager; - private Drawable error; - private boolean forceDownload; - private boolean noCustomImage; - - private Builder(@NonNull RequestManager requestManager, Artist artist) { - this.requestManager = requestManager; - this.artist = artist; - error = - TintHelper.createTintedDrawable( - ContextCompat.getDrawable(App.Companion.getContext(), R.drawable.default_artist_art), - ThemeStore.Companion.accentColor(App.Companion.getContext())); - } - - public static Builder from(@NonNull RequestManager requestManager, Artist artist) { - return new Builder(requestManager, artist); - } - - public BitmapBuilder asBitmap() { - return new BitmapBuilder(this); - } - - public DrawableRequestBuilder build() { - //noinspection unchecked - return createBaseRequest(requestManager, artist, noCustomImage, forceDownload) - .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) - .animate(DEFAULT_ANIMATION) - .error(DEFAULT_ERROR_IMAGE) - .priority(Priority.LOW) - .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) - .dontTransform() - .signature(createSignature(artist)); - } - - public Builder forceDownload(boolean forceDownload) { - this.forceDownload = forceDownload; - return this; - } - - public PaletteBuilder generatePalette(Context context) { - return new PaletteBuilder(this, context); - } - - public Builder noCustomImage(boolean noCustomImage) { - this.noCustomImage = noCustomImage; - return this; - } - } - - public static class BitmapBuilder { - - private final Builder builder; - - BitmapBuilder(Builder builder) { - this.builder = builder; - } - - public BitmapRequestBuilder build() { - //noinspection unchecked - return createBaseRequest( - builder.requestManager, builder.artist, builder.noCustomImage, builder.forceDownload) - .asBitmap() - .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) - .animate(DEFAULT_ANIMATION) - .error(DEFAULT_ERROR_IMAGE) - .priority(Priority.LOW) - .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) - .dontTransform() - .signature(createSignature(builder.artist)); - } - } - - public static class PaletteBuilder { - - final Context context; - - private final Builder builder; - - PaletteBuilder(Builder builder, Context context) { - this.builder = builder; - this.context = context; - } - - public BitmapRequestBuilder build() { - //noinspection unchecked - return createBaseRequest( - builder.requestManager, builder.artist, builder.noCustomImage, builder.forceDownload) - .asBitmap() - .transcode(new BitmapPaletteTranscoder(context), BitmapPaletteWrapper.class) - .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) - .error(DEFAULT_ERROR_IMAGE) - .animate(DEFAULT_ANIMATION) - .priority(Priority.LOW) - .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) - .dontTransform() - .signature(createSignature(builder.artist)); - } - } -} diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/BlurTransformation.kt b/app/src/main/java/code/name/monkey/retromusic/glide/BlurTransformation.kt index 0996b553..a6fb8957 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/BlurTransformation.kt +++ b/app/src/main/java/code/name/monkey/retromusic/glide/BlurTransformation.kt @@ -1,24 +1,9 @@ -/* - * 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.glide import android.content.Context import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Paint -import android.os.Build import android.renderscript.* import androidx.annotation.FloatRange import code.name.monkey.retromusic.BuildConfig @@ -26,12 +11,14 @@ import code.name.monkey.retromusic.helper.StackBlur import code.name.monkey.retromusic.util.ImageUtil import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool import com.bumptech.glide.load.resource.bitmap.BitmapTransformation +import java.security.MessageDigest + class BlurTransformation : BitmapTransformation { private var context: Context? = null - private var blurRadius: Float = 0.toFloat() - private var sampling: Int = 0 + private var blurRadius = 0f + private var sampling = 0 private fun init(builder: Builder) { this.context = builder.context @@ -39,18 +26,18 @@ class BlurTransformation : BitmapTransformation { this.sampling = builder.sampling } - private constructor(builder: Builder) : super(builder.context) { + private constructor(builder: Builder) : super() { init(builder) } - private constructor(builder: Builder, bitmapPool: BitmapPool) : super(bitmapPool) { + private constructor(builder: Builder, bitmapPool: BitmapPool) : super() { init(builder) } class Builder(val context: Context) { private var bitmapPool: BitmapPool? = null var blurRadius = DEFAULT_BLUR_RADIUS - var sampling: Int = 0 + var sampling = 0 /** * @param blurRadius The radius to use. Must be between 0 and 25. Default is 5. @@ -74,7 +61,7 @@ class BlurTransformation : BitmapTransformation { * @param bitmapPool The BitmapPool to use. * @return the same Builder */ - fun bitmapPool(bitmapPool: BitmapPool): Builder { + fun bitmapPool(bitmapPool: BitmapPool?): Builder { this.bitmapPool = bitmapPool return this } @@ -91,65 +78,60 @@ class BlurTransformation : BitmapTransformation { toTransform: Bitmap, outWidth: Int, outHeight: Int - ): Bitmap? { - val sampling: Int - if (this.sampling == 0) { - sampling = ImageUtil.calculateInSampleSize(toTransform.width, toTransform.height, 100) + ): Bitmap { + val sampling = if (this.sampling == 0) { + ImageUtil.calculateInSampleSize(toTransform.width, toTransform.height, 100) } else { - sampling = this.sampling + this.sampling } - val width = toTransform.width val height = toTransform.height val scaledWidth = width / sampling val scaledHeight = height / sampling - - var out: Bitmap? = pool.get(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888) - if (out == null) { - out = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888) - } - - val canvas = Canvas(out!!) + val out = pool[scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888] + val canvas = Canvas(out) canvas.scale(1 / sampling.toFloat(), 1 / sampling.toFloat()) val paint = Paint() paint.flags = Paint.FILTER_BITMAP_FLAG canvas.drawBitmap(toTransform, 0f, 0f, paint) - if (Build.VERSION.SDK_INT >= 17) { - try { - val rs = RenderScript.create(context!!.applicationContext) - val input = Allocation.createFromBitmap( - rs, - out, - Allocation.MipmapControl.MIPMAP_NONE, - Allocation.USAGE_SCRIPT - ) - val output = Allocation.createTyped(rs, input.type) - val script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)) + try { + val rs = RenderScript.create(context!!.applicationContext) + val input = Allocation.createFromBitmap( + rs, + out, + Allocation.MipmapControl.MIPMAP_NONE, + Allocation.USAGE_SCRIPT + ) + val output = Allocation.createTyped(rs, input.type) + val script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)) - script.setRadius(blurRadius) - script.setInput(input) - script.forEach(output) + script.setRadius(blurRadius) + script.setInput(input) + script.forEach(output) - output.copyTo(out) + output.copyTo(out) - rs.destroy() + rs.destroy() - return out - } catch (e: RSRuntimeException) { - // on some devices RenderScript.create() throws: android.support.v8.renderscript.RSRuntimeException: Error loading libRSSupport library - if (BuildConfig.DEBUG) e.printStackTrace() - } + return out + } catch (e: RSRuntimeException) { + // on some devices RenderScript.create() throws: android.support.v8.renderscript.RSRuntimeException: Error loading libRSSupport library + if (BuildConfig.DEBUG) e.printStackTrace() } return StackBlur.blur(out, blurRadius) } - override fun getId(): String { - return "BlurTransformation(radius=$blurRadius, sampling=$sampling)" + override fun updateDiskCacheKey(messageDigest: MessageDigest) { + messageDigest.update( + "BlurTransformation(radius=$blurRadius, sampling=$sampling)".toByteArray( + CHARSET + ) + ) } companion object { - val DEFAULT_BLUR_RADIUS = 5f + const val DEFAULT_BLUR_RADIUS = 5f } } diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/ProfileBannerGlideRequest.java b/app/src/main/java/code/name/monkey/retromusic/glide/ProfileBannerGlideRequest.java deleted file mode 100644 index 8a27576a..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/glide/ProfileBannerGlideRequest.java +++ /dev/null @@ -1,76 +0,0 @@ -package code.name.monkey.retromusic.glide; - -import static code.name.monkey.retromusic.Constants.USER_BANNER; - -import android.graphics.Bitmap; -import androidx.annotation.NonNull; -import code.name.monkey.retromusic.App; -import code.name.monkey.retromusic.R; -import com.bumptech.glide.BitmapRequestBuilder; -import com.bumptech.glide.BitmapTypeRequest; -import com.bumptech.glide.RequestManager; -import com.bumptech.glide.load.Key; -import com.bumptech.glide.load.engine.DiskCacheStrategy; -import com.bumptech.glide.signature.MediaStoreSignature; -import java.io.File; - -public class ProfileBannerGlideRequest { - private static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.NONE; - private static final int DEFAULT_ERROR_IMAGE = R.drawable.material_design_default; - private static final int DEFAULT_ANIMATION = android.R.anim.fade_in; - - public static File getBannerModel() { - File dir = App.Companion.getContext().getFilesDir(); - return new File(dir, USER_BANNER); - } - - private static BitmapTypeRequest createBaseRequest( - RequestManager requestManager, File profile) { - return requestManager.load(profile).asBitmap(); - } - - private static Key createSignature(File file) { - return new MediaStoreSignature("", file.lastModified(), 0); - } - - public static class Builder { - private RequestManager requestManager; - private File profile; - - private Builder(RequestManager requestManager, File profile) { - this.requestManager = requestManager; - this.profile = profile; - } - - public static Builder from(@NonNull RequestManager requestManager, File profile) { - return new Builder(requestManager, profile); - } - - @NonNull - public BitmapRequestBuilder build() { - //noinspection unchecked - return createBaseRequest(requestManager, profile) - .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) - .placeholder(DEFAULT_ERROR_IMAGE) - .animate(DEFAULT_ANIMATION) - .signature(createSignature(profile)); - } - } - - public static class BitmapBuilder { - private final Builder builder; - - BitmapBuilder(Builder builder) { - this.builder = builder; - } - - public BitmapRequestBuilder build() { - //noinspection unchecked - return createBaseRequest(builder.requestManager, builder.profile) - .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) - .error(DEFAULT_ERROR_IMAGE) - .animate(DEFAULT_ANIMATION) - .signature(createSignature(builder.profile)); - } - } -} diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/RetroGlideExtension.kt b/app/src/main/java/code/name/monkey/retromusic/glide/RetroGlideExtension.kt new file mode 100644 index 00000000..3f5edc8c --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/glide/RetroGlideExtension.kt @@ -0,0 +1,192 @@ +package code.name.monkey.retromusic.glide + +import android.graphics.drawable.Drawable +import code.name.monkey.appthemehelper.ThemeStore.Companion.accentColor +import code.name.monkey.appthemehelper.util.TintHelper +import code.name.monkey.retromusic.App.Companion.getContext +import code.name.monkey.retromusic.Constants.USER_BANNER +import code.name.monkey.retromusic.Constants.USER_PROFILE +import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.glide.artistimage.ArtistImage +import code.name.monkey.retromusic.glide.audiocover.AudioFileCover +import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper +import code.name.monkey.retromusic.model.Artist +import code.name.monkey.retromusic.model.Song +import code.name.monkey.retromusic.util.ArtistSignatureUtil +import code.name.monkey.retromusic.util.CustomArtistImageUtil.Companion.getFile +import code.name.monkey.retromusic.util.CustomArtistImageUtil.Companion.getInstance +import code.name.monkey.retromusic.util.MusicUtil.getMediaStoreAlbumCoverUri +import code.name.monkey.retromusic.util.PreferenceUtil +import com.bumptech.glide.GenericTransitionOptions +import com.bumptech.glide.Priority +import com.bumptech.glide.RequestBuilder +import com.bumptech.glide.annotation.GlideExtension +import com.bumptech.glide.annotation.GlideOption +import com.bumptech.glide.annotation.GlideType +import com.bumptech.glide.load.Key +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.request.BaseRequestOptions +import com.bumptech.glide.request.target.Target.SIZE_ORIGINAL +import com.bumptech.glide.signature.MediaStoreSignature +import java.io.File + + +@GlideExtension +object RetroGlideExtension { + + private const val DEFAULT_ERROR_ARTIST_IMAGE = + R.drawable.default_artist_art + private const val DEFAULT_ERROR_SONG_IMAGE: Int = R.drawable.default_audio_art + private const val DEFAULT_ERROR_ALBUM_IMAGE = R.drawable.default_album_art + private const val DEFAULT_ERROR_IMAGE_BANNER = R.drawable.material_design_default + + private val DEFAULT_DISK_CACHE_STRATEGY_ARTIST = DiskCacheStrategy.RESOURCE + private val DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.NONE + + private const val DEFAULT_ANIMATION = android.R.anim.fade_in + + @JvmStatic + @GlideType(BitmapPaletteWrapper::class) + fun asBitmapPalette(requestBuilder: RequestBuilder): RequestBuilder { + return requestBuilder + } + + private fun getSongModel(song: Song, ignoreMediaStore: Boolean): Any { + return if (ignoreMediaStore) { + AudioFileCover(song.data) + } else { + getMediaStoreAlbumCoverUri(song.albumId) + } + } + + fun getSongModel(song: Song): Any { + return getSongModel(song, PreferenceUtil.isIgnoreMediaStoreArtwork) + } + + fun getArtistModel(artist: Artist): Any { + return getArtistModel( + artist, + getInstance(getContext()).hasCustomArtistImage(artist), + false + ) + } + + fun getArtistModel(artist: Artist, forceDownload: Boolean): Any { + return getArtistModel( + artist, + getInstance(getContext()).hasCustomArtistImage(artist), + forceDownload + ) + } + + private fun getArtistModel(artist: Artist, hasCustomImage: Boolean, forceDownload: Boolean): Any { + return if (!hasCustomImage) { + ArtistImage(artist) + } else { + getFile(artist) + } + } + + @JvmStatic + @GlideOption + fun artistImageOptions( + baseRequestOptions: BaseRequestOptions<*>, + artist: Artist + ): BaseRequestOptions<*> { + return baseRequestOptions + .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY_ARTIST) + .priority(Priority.LOW) + .error(DEFAULT_ERROR_ARTIST_IMAGE) + .override(SIZE_ORIGINAL, SIZE_ORIGINAL) + .signature(createSignature(artist)) + } + + @JvmStatic + @GlideOption + fun songCoverOptions( + baseRequestOptions: BaseRequestOptions<*>, + song: Song + ): BaseRequestOptions<*> { + return baseRequestOptions.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .error(DEFAULT_ERROR_SONG_IMAGE) + .signature(createSignature(song)) + } + + @JvmStatic + @GlideOption + fun albumCoverOptions( + baseRequestOptions: BaseRequestOptions<*>, + song: Song + ): BaseRequestOptions<*> { + return baseRequestOptions.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .error(DEFAULT_ERROR_ALBUM_IMAGE) + .signature(createSignature(song)) + } + + @JvmStatic + @GlideOption + fun userProfileOptions( + baseRequestOptions: BaseRequestOptions<*>, + file: File + ): BaseRequestOptions<*> { + return baseRequestOptions.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .error(getErrorUserProfile()) + .signature(createSignature(file)) + } + + @JvmStatic + @GlideOption + fun profileBannerOptions( + baseRequestOptions: BaseRequestOptions<*>, + file: File + ): BaseRequestOptions<*> { + return baseRequestOptions.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .placeholder(DEFAULT_ERROR_IMAGE_BANNER) + .error(DEFAULT_ERROR_IMAGE_BANNER) + .signature(createSignature(file)) + } + + @JvmStatic + @GlideOption + fun playlistOptions( + baseRequestOptions: BaseRequestOptions<*> + ): BaseRequestOptions<*> { + return baseRequestOptions.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .error(DEFAULT_ERROR_ALBUM_IMAGE) + } + + private fun createSignature(song: Song): Key { + return MediaStoreSignature("", song.dateModified, 0) + } + + private fun createSignature(file: File): Key { + return MediaStoreSignature("", file.lastModified(), 0) + } + + private fun createSignature(artist: Artist): Key { + return ArtistSignatureUtil.getInstance(getContext()) + .getArtistSignature(artist.name) + } + + fun getUserModel(): File { + val dir = getContext().filesDir + return File(dir, USER_PROFILE) + } + + fun getBannerModel(): File { + val dir = getContext().filesDir + return File(dir, USER_BANNER) + } + + private fun getErrorUserProfile(): Drawable { + return TintHelper.createTintedDrawable( + getContext(), + R.drawable.ic_account, + accentColor(getContext()) + ) + } + + fun getDefaultTransition(): GenericTransitionOptions { + return GenericTransitionOptions().transition(DEFAULT_ANIMATION) + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicColoredTarget.kt b/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicColoredTarget.kt index 023dc396..195f7010 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicColoredTarget.kt +++ b/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicColoredTarget.kt @@ -22,7 +22,7 @@ import code.name.monkey.retromusic.R import code.name.monkey.retromusic.glide.palette.BitmapPaletteTarget import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper import code.name.monkey.retromusic.util.color.MediaNotificationProcessor -import com.bumptech.glide.request.animation.GlideAnimation +import com.bumptech.glide.request.transition.Transition abstract class RetroMusicColoredTarget(view: ImageView) : BitmapPaletteTarget(view) { @@ -31,21 +31,19 @@ abstract class RetroMusicColoredTarget(view: ImageView) : BitmapPaletteTarget(vi abstract fun onColorReady(colors: MediaNotificationProcessor) - override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) { - super.onLoadFailed(e, errorDrawable) + override fun onLoadFailed(errorDrawable: Drawable?) { + super.onLoadFailed(errorDrawable) val colors = MediaNotificationProcessor(App.getContext(), errorDrawable) onColorReady(colors) } override fun onResourceReady( - resource: BitmapPaletteWrapper?, - glideAnimation: GlideAnimation? + resource: BitmapPaletteWrapper, + transition: Transition? ) { - super.onResourceReady(resource, glideAnimation) - resource?.let { bitmapWrap -> - MediaNotificationProcessor(App.getContext()).getPaletteAsync({ - onColorReady(it) - }, bitmapWrap.bitmap) - } + super.onResourceReady(resource, transition) + MediaNotificationProcessor(App.getContext()).getPaletteAsync({ + onColorReady(it) + }, resource.bitmap) } } diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicGlideModule.kt b/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicGlideModule.kt index 74fbe565..8f663089 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicGlideModule.kt +++ b/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicGlideModule.kt @@ -15,25 +15,38 @@ package code.name.monkey.retromusic.glide import android.content.Context +import android.graphics.Bitmap import code.name.monkey.retromusic.glide.artistimage.ArtistImage import code.name.monkey.retromusic.glide.artistimage.Factory import code.name.monkey.retromusic.glide.audiocover.AudioFileCover import code.name.monkey.retromusic.glide.audiocover.AudioFileCoverLoader +import code.name.monkey.retromusic.glide.palette.BitmapPaletteTranscoder +import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper +import code.name.monkey.retromusic.glide.playlistPreview.PlaylistPreview +import code.name.monkey.retromusic.glide.playlistPreview.PlaylistPreviewLoader import com.bumptech.glide.Glide -import com.bumptech.glide.GlideBuilder -import com.bumptech.glide.module.GlideModule +import com.bumptech.glide.Registry +import com.bumptech.glide.annotation.GlideModule +import com.bumptech.glide.module.AppGlideModule import java.io.InputStream -class RetroMusicGlideModule : GlideModule { - override fun applyOptions(context: Context, builder: GlideBuilder) { - } - - override fun registerComponents(context: Context, glide: Glide) { - glide.register( +@GlideModule +class RetroMusicGlideModule : AppGlideModule() { + override fun registerComponents(context: Context, glide: Glide, registry: Registry) { + registry.prepend(PlaylistPreview::class.java, Bitmap::class.java, PlaylistPreviewLoader.Factory(context)) + registry.prepend( AudioFileCover::class.java, InputStream::class.java, AudioFileCoverLoader.Factory() ) - glide.register(ArtistImage::class.java, InputStream::class.java, Factory(context)) + registry.prepend(ArtistImage::class.java, InputStream::class.java, Factory(context)) + registry.register( + Bitmap::class.java, + BitmapPaletteWrapper::class.java, BitmapPaletteTranscoder() + ) } -} + + override fun isManifestParsingEnabled(): Boolean { + return false + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/SingleColorTarget.kt b/app/src/main/java/code/name/monkey/retromusic/glide/SingleColorTarget.kt index 989419fb..4991418e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/SingleColorTarget.kt +++ b/app/src/main/java/code/name/monkey/retromusic/glide/SingleColorTarget.kt @@ -21,7 +21,7 @@ import code.name.monkey.retromusic.R import code.name.monkey.retromusic.glide.palette.BitmapPaletteTarget import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper import code.name.monkey.retromusic.util.ColorUtil -import com.bumptech.glide.request.animation.GlideAnimation +import com.bumptech.glide.request.transition.Transition abstract class SingleColorTarget(view: ImageView) : BitmapPaletteTarget(view) { @@ -30,23 +30,21 @@ abstract class SingleColorTarget(view: ImageView) : BitmapPaletteTarget(view) { abstract fun onColorReady(color: Int) - override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) { - super.onLoadFailed(e, errorDrawable) + override fun onLoadFailed(errorDrawable: Drawable?) { + super.onLoadFailed(errorDrawable) onColorReady(defaultFooterColor) } override fun onResourceReady( - resource: BitmapPaletteWrapper?, - glideAnimation: GlideAnimation? + resource: BitmapPaletteWrapper, + transition: Transition? ) { - super.onResourceReady(resource, glideAnimation) - resource?.let { - onColorReady( - ColorUtil.getColor( - it.palette, - ATHUtil.resolveColor(view.context, R.attr.colorPrimary) - ) + super.onResourceReady(resource, transition) + onColorReady( + ColorUtil.getColor( + resource.palette, + ATHUtil.resolveColor(view.context, R.attr.colorPrimary) ) - } + ) } } diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/SongGlideRequest.java b/app/src/main/java/code/name/monkey/retromusic/glide/SongGlideRequest.java deleted file mode 100644 index 5f424e4a..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/glide/SongGlideRequest.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (c) 2019 Hemanth Savarala. - * - * 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.glide; - -import android.content.Context; -import android.graphics.Bitmap; -import androidx.annotation.NonNull; -import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.glide.audiocover.AudioFileCover; -import code.name.monkey.retromusic.glide.palette.BitmapPaletteTranscoder; -import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper; -import code.name.monkey.retromusic.model.Song; -import code.name.monkey.retromusic.util.MusicUtil; -import code.name.monkey.retromusic.util.PreferenceUtil; -import com.bumptech.glide.BitmapRequestBuilder; -import com.bumptech.glide.DrawableRequestBuilder; -import com.bumptech.glide.DrawableTypeRequest; -import com.bumptech.glide.RequestManager; -import com.bumptech.glide.load.Key; -import com.bumptech.glide.load.engine.DiskCacheStrategy; -import com.bumptech.glide.load.resource.drawable.GlideDrawable; -import com.bumptech.glide.signature.MediaStoreSignature; - -/** Created by hemanths on 2019-09-15. */ -public class SongGlideRequest { - - private static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.NONE; - private static final int DEFAULT_ERROR_IMAGE = R.drawable.default_audio_art; - private static final int DEFAULT_ANIMATION = android.R.anim.fade_in; - - @NonNull - private static DrawableTypeRequest createBaseRequest( - @NonNull RequestManager requestManager, @NonNull Song song, boolean ignoreMediaStore) { - if (ignoreMediaStore) { - return requestManager.load(new AudioFileCover(song.getData())); - } else { - return requestManager.loadFromMediaStore( - MusicUtil.INSTANCE.getMediaStoreAlbumCoverUri(song.getAlbumId())); - } - } - - @NonNull - private static Key createSignature(@NonNull Song song) { - return new MediaStoreSignature("", song.getDateModified(), 0); - } - - public static class Builder { - final RequestManager requestManager; - final Song song; - boolean ignoreMediaStore; - - private Builder(@NonNull RequestManager requestManager, Song song) { - this.requestManager = requestManager; - this.song = song; - } - - @NonNull - public static Builder from(@NonNull RequestManager requestManager, Song song) { - return new Builder(requestManager, song); - } - - @NonNull - public PaletteBuilder generatePalette(@NonNull Context context) { - return new PaletteBuilder(this, context); - } - - @NonNull - public BitmapBuilder asBitmap() { - return new BitmapBuilder(this); - } - - @NonNull - public Builder checkIgnoreMediaStore(@NonNull Context context) { - return ignoreMediaStore(PreferenceUtil.INSTANCE.isIgnoreMediaStoreArtwork()); - } - - @NonNull - public Builder ignoreMediaStore(boolean ignoreMediaStore) { - this.ignoreMediaStore = ignoreMediaStore; - return this; - } - - @NonNull - public DrawableRequestBuilder build() { - //noinspection unchecked - return createBaseRequest(requestManager, song, ignoreMediaStore) - .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) - .error(DEFAULT_ERROR_IMAGE) - .animate(DEFAULT_ANIMATION) - .signature(createSignature(song)); - } - } - - public static class BitmapBuilder { - private final Builder builder; - - BitmapBuilder(Builder builder) { - this.builder = builder; - } - - public BitmapRequestBuilder build() { - //noinspection unchecked - return createBaseRequest(builder.requestManager, builder.song, builder.ignoreMediaStore) - .asBitmap() - .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) - .error(DEFAULT_ERROR_IMAGE) - .animate(DEFAULT_ANIMATION) - .signature(createSignature(builder.song)); - } - } - - public static class PaletteBuilder { - final Context context; - private final Builder builder; - - PaletteBuilder(Builder builder, Context context) { - this.builder = builder; - this.context = context; - } - - public BitmapRequestBuilder build() { - //noinspection unchecked - return createBaseRequest(builder.requestManager, builder.song, builder.ignoreMediaStore) - .asBitmap() - .transcode(new BitmapPaletteTranscoder(context), BitmapPaletteWrapper.class) - .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) - .error(DEFAULT_ERROR_IMAGE) - .animate(DEFAULT_ANIMATION) - .signature(createSignature(builder.song)); - } - } -} diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/UserProfileGlideRequest.java b/app/src/main/java/code/name/monkey/retromusic/glide/UserProfileGlideRequest.java deleted file mode 100644 index f3b2fd42..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/glide/UserProfileGlideRequest.java +++ /dev/null @@ -1,83 +0,0 @@ -package code.name.monkey.retromusic.glide; - -import static code.name.monkey.retromusic.Constants.USER_PROFILE; - -import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; -import androidx.annotation.NonNull; -import code.name.monkey.appthemehelper.ThemeStore; -import code.name.monkey.appthemehelper.util.TintHelper; -import code.name.monkey.retromusic.App; -import code.name.monkey.retromusic.R; -import com.bumptech.glide.BitmapRequestBuilder; -import com.bumptech.glide.BitmapTypeRequest; -import com.bumptech.glide.RequestManager; -import com.bumptech.glide.load.Key; -import com.bumptech.glide.load.engine.DiskCacheStrategy; -import com.bumptech.glide.signature.MediaStoreSignature; -import java.io.File; - -public class UserProfileGlideRequest { - private static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.NONE; - private static final int DEFAULT_ERROR_IMAGE = R.drawable.ic_account; - private static final int DEFAULT_ANIMATION = android.R.anim.fade_in; - - public static File getUserModel() { - File dir = App.Companion.getContext().getFilesDir(); - return new File(dir, USER_PROFILE); - } - - private static BitmapTypeRequest createBaseRequest( - RequestManager requestManager, File profile) { - return requestManager.load(profile).asBitmap(); - } - - private static Key createSignature(File file) { - return new MediaStoreSignature("", file.lastModified(), 0); - } - - public static class Builder { - private RequestManager requestManager; - private File profile; - private Drawable error; - - private Builder(RequestManager requestManager, File profile) { - this.requestManager = requestManager; - this.profile = profile; - error = - TintHelper.createTintedDrawable( - App.Companion.getContext(), - R.drawable.ic_account, - ThemeStore.Companion.accentColor(App.Companion.getContext())); - } - - public static Builder from(@NonNull RequestManager requestManager, File profile) { - return new Builder(requestManager, profile); - } - - @NonNull - public BitmapRequestBuilder build() { - return createBaseRequest(requestManager, profile) - .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) - .error(error) - .animate(DEFAULT_ANIMATION) - .signature(createSignature(profile)); - } - } - - public static class BitmapBuilder { - private final Builder builder; - - BitmapBuilder(Builder builder) { - this.builder = builder; - } - - public BitmapRequestBuilder build() { - return createBaseRequest(builder.requestManager, builder.profile) - .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) - .error(builder.error) - .animate(DEFAULT_ANIMATION) - .signature(createSignature(builder.profile)); - } - } -} diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImage.kt b/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImage.kt new file mode 100644 index 00000000..da9de7d4 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImage.kt @@ -0,0 +1,16 @@ +package code.name.monkey.retromusic.glide.artistimage + +import code.name.monkey.retromusic.model.Artist + +class ArtistImage(val artist: Artist){ + override fun equals(other: Any?): Boolean { + if (other is ArtistImage){ + return other.artist == artist + } + return false + } + + override fun hashCode(): Int { + return artist.hashCode() + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageFetcher.kt b/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageFetcher.kt new file mode 100644 index 00000000..19449ff1 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageFetcher.kt @@ -0,0 +1,121 @@ +package code.name.monkey.retromusic.glide.artistimage + +import android.content.Context +import code.name.monkey.retromusic.model.Data +import code.name.monkey.retromusic.model.DeezerResponse +import code.name.monkey.retromusic.network.DeezerService +import code.name.monkey.retromusic.util.MusicUtil +import code.name.monkey.retromusic.util.PreferenceUtil +import com.bumptech.glide.Priority +import com.bumptech.glide.integration.okhttp3.OkHttpStreamFetcher +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.data.DataFetcher +import com.bumptech.glide.load.model.GlideUrl +import okhttp3.OkHttpClient +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import java.io.FileNotFoundException +import java.io.IOException +import java.io.InputStream + + +class ArtistImageFetcher( + private val context: Context, + private val deezerService: DeezerService, + val model: ArtistImage, + private val okhttp: OkHttpClient +) : DataFetcher { + + private var streamFetcher: OkHttpStreamFetcher? = null + private var response: Call? = null + private var isCancelled: Boolean = false + + override fun getDataClass(): Class { + return InputStream::class.java + } + + override fun getDataSource(): DataSource { + return DataSource.REMOTE + } + + override fun loadData(priority: Priority, callback: DataFetcher.DataCallback) { + try { + if (!MusicUtil.isArtistNameUnknown(model.artist.name) && + PreferenceUtil.isAllowedToDownloadMetadata() + ) { + val artists = model.artist.name.split(",", "&") + response = deezerService.getArtistImage(artists[0]) + response?.enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (!response.isSuccessful) { + throw IOException("Request failed with code: " + response.code()) + } + + if (isCancelled) { + callback.onDataReady(null) + return + } + + try { + val deezerResponse = response.body() + val imageUrl = + deezerResponse?.data?.get(0)?.let { getHighestQuality(it) } + // Fragile way to detect a place holder image returned from Deezer: + // ex: "https://e-cdns-images.dzcdn.net/images/artist//250x250-000000-80-0-0.jpg" + // the double slash implies no artist identified + val placeHolder = imageUrl?.contains("/images/artist//") ?: false + if (!placeHolder) { + streamFetcher = OkHttpStreamFetcher(okhttp, GlideUrl(imageUrl)) + streamFetcher?.loadData(priority, callback) + } else { + callback.onDataReady(getFallbackAlbumImage()) + } + } catch (e: Exception) { + callback.onDataReady(getFallbackAlbumImage()) + } + } + + override fun onFailure(call: Call, t: Throwable) { + callback.onDataReady(getFallbackAlbumImage()) + } + }) + } else callback.onDataReady(null) + } catch (e: Exception) { + callback.onLoadFailed(e) + } + } + + private fun getFallbackAlbumImage(): InputStream? { + val imageUri = MusicUtil.getMediaStoreAlbumCoverUri(model.artist.safeGetFirstAlbum().id) + return try { + context.contentResolver.openInputStream(imageUri) + } catch (e: FileNotFoundException){ + null + } + } + + private fun getHighestQuality(imageUrl: Data): String { + return when { + imageUrl.pictureXl.isNotEmpty() -> imageUrl.pictureXl + imageUrl.pictureBig.isNotEmpty() -> imageUrl.pictureBig + imageUrl.pictureMedium.isNotEmpty() -> imageUrl.pictureMedium + imageUrl.pictureSmall.isNotEmpty() -> imageUrl.pictureSmall + imageUrl.picture.isNotEmpty() -> imageUrl.picture + else -> "" + } + } + + override fun cleanup() { + streamFetcher?.cleanup() + } + + override fun cancel() { + isCancelled = true + response?.cancel() + streamFetcher?.cancel() + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageLoader.kt b/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageLoader.kt index c07beeb7..d2fff90d 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageLoader.kt +++ b/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageLoader.kt @@ -15,115 +15,39 @@ package code.name.monkey.retromusic.glide.artistimage import android.content.Context -import code.name.monkey.retromusic.model.Artist -import code.name.monkey.retromusic.model.Data import code.name.monkey.retromusic.network.DeezerService -import code.name.monkey.retromusic.util.MusicUtil -import code.name.monkey.retromusic.util.PreferenceUtil -import com.bumptech.glide.Priority -import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader -import com.bumptech.glide.load.data.DataFetcher -import com.bumptech.glide.load.model.GenericLoaderFactory -import com.bumptech.glide.load.model.GlideUrl +import com.bumptech.glide.load.Options import com.bumptech.glide.load.model.ModelLoader +import com.bumptech.glide.load.model.ModelLoader.LoadData import com.bumptech.glide.load.model.ModelLoaderFactory -import com.bumptech.glide.load.model.stream.StreamModelLoader -import java.io.IOException -import java.io.InputStream -import java.util.concurrent.TimeUnit +import com.bumptech.glide.load.model.MultiModelLoaderFactory +import com.bumptech.glide.signature.ObjectKey import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor - -class ArtistImage(val artist: Artist) - -class ArtistImageFetcher( - private val context: Context, - private val deezerService: DeezerService, - val model: ArtistImage, - val urlLoader: ModelLoader, - val width: Int, - val height: Int -) : DataFetcher { - - private var urlFetcher: DataFetcher? = null - private var isCancelled: Boolean = false - - override fun cleanup() { - urlFetcher?.cleanup() - } - - override fun getId(): String { - return model.artist.name - } - - override fun cancel() { - isCancelled = true - urlFetcher?.cancel() - } - - override fun loadData(priority: Priority?): InputStream? { - if (!MusicUtil.isArtistNameUnknown(model.artist.name) && - PreferenceUtil.isAllowedToDownloadMetadata() - ) { - val artists = model.artist.name.split(",") - val response = deezerService.getArtistImage(artists[0]).execute() - - if (!response.isSuccessful) { - throw IOException("Request failed with code: " + response.code()) - } - - if (isCancelled) return null - - return try { - val deezerResponse = response.body() - val imageUrl = deezerResponse?.data?.get(0)?.let { getHighestQuality(it) } - // Fragile way to detect a place holder image returned from Deezer: - // ex: "https://e-cdns-images.dzcdn.net/images/artist//250x250-000000-80-0-0.jpg" - // the double slash implies no artist identified - val placeHolder = imageUrl?.contains("/images/artist//") ?: false - if (!placeHolder) { - val glideUrl = GlideUrl(imageUrl) - urlFetcher = urlLoader.getResourceFetcher(glideUrl, width, height) - urlFetcher?.loadData(priority) - } else { - getFallbackAlbumImage() - } - } catch (e: Exception) { - getFallbackAlbumImage() - } - } else return null - } - - private fun getFallbackAlbumImage(): InputStream? { - val imageUri = MusicUtil.getMediaStoreAlbumCoverUri(model.artist.safeGetFirstAlbum().id) - return context.contentResolver.openInputStream(imageUri) - } - - private fun getHighestQuality(imageUrl: Data): String { - return when { - imageUrl.pictureXl.isNotEmpty() -> imageUrl.pictureXl - imageUrl.pictureBig.isNotEmpty() -> imageUrl.pictureBig - imageUrl.pictureMedium.isNotEmpty() -> imageUrl.pictureMedium - imageUrl.pictureSmall.isNotEmpty() -> imageUrl.pictureSmall - imageUrl.picture.isNotEmpty() -> imageUrl.picture - else -> "" - } - } -} +import java.io.InputStream +import java.util.concurrent.TimeUnit class ArtistImageLoader( val context: Context, private val deezerService: DeezerService, - private val urlLoader: ModelLoader -) : StreamModelLoader { + private val okhttp: OkHttpClient +) : ModelLoader { - override fun getResourceFetcher( + override fun buildLoadData( model: ArtistImage, width: Int, - height: Int - ): DataFetcher { - return ArtistImageFetcher(context, deezerService, model, urlLoader, width, height) + height: Int, + options: Options + ): LoadData { + return LoadData( + ObjectKey(model.artist.name), + ArtistImageFetcher(context, deezerService, model, okhttp) + ) + } + + override fun handles(model: ArtistImage): Boolean { + return true } } @@ -132,16 +56,15 @@ class Factory( ) : ModelLoaderFactory { private var deezerService: DeezerService - private var okHttpFactory: OkHttpUrlLoader.Factory + private var okHttp: OkHttpClient init { - okHttpFactory = OkHttpUrlLoader.Factory( + okHttp = OkHttpClient.Builder() .connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS) .readTimeout(TIMEOUT, TimeUnit.MILLISECONDS) .writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS) .build() - ) deezerService = DeezerService.invoke( DeezerService.createDefaultOkHttpClient(context) .connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS) @@ -158,23 +81,18 @@ class Factory( return interceptor } - override fun build( - context: Context?, - factories: GenericLoaderFactory? - ): ModelLoader { + override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader { return ArtistImageLoader( - context!!, + context, deezerService, - okHttpFactory.build(context, factories) + okHttp ) } - override fun teardown() { - okHttpFactory.teardown() - } + override fun teardown() {} companion object { // we need these very low values to make sure our artist image loading calls doesn't block the image loading queue - private const val TIMEOUT: Long = 700 + private const val TIMEOUT: Long = 500 } } diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCover.java b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCover.java index 9a5fd6a0..346e0591 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCover.java +++ b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCover.java @@ -14,6 +14,8 @@ package code.name.monkey.retromusic.glide.audiocover; +import androidx.annotation.Nullable; + /** @author Karim Abou Zeid (kabouzeid) */ public class AudioFileCover { public final String filePath; @@ -21,4 +23,17 @@ public class AudioFileCover { public AudioFileCover(String filePath) { this.filePath = filePath; } + + @Override + public int hashCode() { + return filePath.hashCode(); + } + + @Override + public boolean equals(@Nullable Object object) { + if (object instanceof AudioFileCover){ + return ((AudioFileCover) object).filePath.equals(filePath); + } + return false; + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverFetcher.java b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverFetcher.java index 5240d1d1..a95bb46a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverFetcher.java +++ b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverFetcher.java @@ -15,9 +15,15 @@ package code.name.monkey.retromusic.glide.audiocover; import android.media.MediaMetadataRetriever; + import com.bumptech.glide.Priority; +import com.bumptech.glide.load.DataSource; import com.bumptech.glide.load.data.DataFetcher; + +import org.jetbrains.annotations.NotNull; + import java.io.ByteArrayInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -27,19 +33,11 @@ public class AudioFileCoverFetcher implements DataFetcher { private InputStream stream; public AudioFileCoverFetcher(AudioFileCover model) { - this.model = model; } @Override - public String getId() { - // makes sure we never ever return null here - return String.valueOf(model.filePath); - } - - @Override - public InputStream loadData(final Priority priority) throws Exception { - + public void loadData(@NotNull Priority priority, @NotNull DataCallback callback) { final MediaMetadataRetriever retriever = new MediaMetadataRetriever(); try { retriever.setDataSource(model.filePath); @@ -49,11 +47,12 @@ public class AudioFileCoverFetcher implements DataFetcher { } else { stream = AudioFileCoverUtils.fallback(model.filePath); } + callback.onDataReady(stream); + } catch (FileNotFoundException e) { + callback.onLoadFailed(e); } finally { retriever.release(); } - - return stream; } @Override @@ -72,4 +71,16 @@ public class AudioFileCoverFetcher implements DataFetcher { public void cancel() { // cannot cancel } + + @NotNull + @Override + public Class getDataClass() { + return InputStream.class; + } + + @NotNull + @Override + public DataSource getDataSource() { + return DataSource.LOCAL; + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverLoader.java b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverLoader.java index 02ffb16d..69c0f907 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverLoader.java +++ b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverLoader.java @@ -14,29 +14,40 @@ package code.name.monkey.retromusic.glide.audiocover; -import android.content.Context; -import com.bumptech.glide.load.data.DataFetcher; -import com.bumptech.glide.load.model.GenericLoaderFactory; +import androidx.annotation.NonNull; + +import com.bumptech.glide.load.Options; import com.bumptech.glide.load.model.ModelLoader; import com.bumptech.glide.load.model.ModelLoaderFactory; -import com.bumptech.glide.load.model.stream.StreamModelLoader; +import com.bumptech.glide.load.model.MultiModelLoaderFactory; +import com.bumptech.glide.signature.ObjectKey; + +import org.jetbrains.annotations.NotNull; + import java.io.InputStream; -public class AudioFileCoverLoader implements StreamModelLoader { +public class AudioFileCoverLoader implements ModelLoader { @Override - public DataFetcher getResourceFetcher(AudioFileCover model, int width, int height) { - return new AudioFileCoverFetcher(model); + public LoadData buildLoadData(@NonNull @NotNull AudioFileCover audioFileCover, int width, int height, @NonNull @NotNull Options options) { + return new LoadData<>(new ObjectKey(audioFileCover.filePath), new AudioFileCoverFetcher(audioFileCover)); + } + + @Override + public boolean handles(@NonNull @NotNull AudioFileCover audioFileCover) { + return audioFileCover.filePath != null; } public static class Factory implements ModelLoaderFactory { + + @NotNull @Override - public ModelLoader build( - Context context, GenericLoaderFactory factories) { + public ModelLoader build(@NonNull @NotNull MultiModelLoaderFactory multiFactory) { return new AudioFileCoverLoader(); } @Override - public void teardown() {} + public void teardown() { + } } } diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverUtils.java b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverUtils.java index aaf612f1..5ffe040c 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverUtils.java +++ b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverUtils.java @@ -14,17 +14,18 @@ package code.name.monkey.retromusic.glide.audiocover; +import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException; +import org.jaudiotagger.audio.exceptions.ReadOnlyFileException; +import org.jaudiotagger.audio.mp3.MP3File; +import org.jaudiotagger.tag.TagException; +import org.jaudiotagger.tag.images.Artwork; + import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException; -import org.jaudiotagger.audio.exceptions.ReadOnlyFileException; -import org.jaudiotagger.audio.mp3.MP3File; -import org.jaudiotagger.tag.TagException; -import org.jaudiotagger.tag.images.Artwork; public class AudioFileCoverUtils { @@ -44,10 +45,7 @@ public class AudioFileCoverUtils { } } // If there are any exceptions, we ignore them and continue to the other fallback method - } catch (ReadOnlyFileException ignored) { - } catch (InvalidAudioFrameException ignored) { - } catch (TagException ignored) { - } catch (IOException ignored) { + } catch (ReadOnlyFileException | InvalidAudioFrameException | TagException | IOException ignored) { } // Method 2: look for album art in external files diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteResource.java b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteResource.java index d94c2c6d..da3c4bb6 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteResource.java +++ b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteResource.java @@ -14,25 +14,31 @@ package code.name.monkey.retromusic.glide.palette; +import androidx.annotation.NonNull; + import com.bumptech.glide.load.engine.Resource; -import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; import com.bumptech.glide.util.Util; public class BitmapPaletteResource implements Resource { private final BitmapPaletteWrapper bitmapPaletteWrapper; - private final BitmapPool bitmapPool; - public BitmapPaletteResource(BitmapPaletteWrapper bitmapPaletteWrapper, BitmapPool bitmapPool) { + public BitmapPaletteResource(BitmapPaletteWrapper bitmapPaletteWrapper) { this.bitmapPaletteWrapper = bitmapPaletteWrapper; - this.bitmapPool = bitmapPool; } + @NonNull @Override public BitmapPaletteWrapper get() { return bitmapPaletteWrapper; } + @NonNull + @Override + public Class getResourceClass() { + return BitmapPaletteWrapper.class; + } + @Override public int getSize() { return Util.getBitmapByteSize(bitmapPaletteWrapper.getBitmap()); @@ -40,8 +46,6 @@ public class BitmapPaletteResource implements Resource { @Override public void recycle() { - if (!bitmapPool.put(bitmapPaletteWrapper.getBitmap())) { - bitmapPaletteWrapper.getBitmap().recycle(); - } + bitmapPaletteWrapper.getBitmap().recycle(); } } diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTarget.java b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTarget.java index 2ce72775..4fb76604 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTarget.java +++ b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTarget.java @@ -15,6 +15,7 @@ package code.name.monkey.retromusic.glide.palette; import android.widget.ImageView; + import com.bumptech.glide.request.target.ImageViewTarget; public class BitmapPaletteTarget extends ImageViewTarget { @@ -24,6 +25,8 @@ public class BitmapPaletteTarget extends ImageViewTarget { @Override protected void setResource(BitmapPaletteWrapper bitmapPaletteWrapper) { - view.setImageBitmap(bitmapPaletteWrapper.getBitmap()); + if (bitmapPaletteWrapper != null) { + view.setImageBitmap(bitmapPaletteWrapper.getBitmap()); + } } } diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTranscoder.java b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTranscoder.java index 7fd4bfad..0e96b809 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTranscoder.java +++ b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTranscoder.java @@ -14,35 +14,23 @@ package code.name.monkey.retromusic.glide.palette; -import android.content.Context; import android.graphics.Bitmap; -import code.name.monkey.retromusic.util.RetroColorUtil; -import com.bumptech.glide.Glide; + +import androidx.annotation.NonNull; + +import com.bumptech.glide.load.Options; import com.bumptech.glide.load.engine.Resource; -import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; import com.bumptech.glide.load.resource.transcode.ResourceTranscoder; -public class BitmapPaletteTranscoder implements ResourceTranscoder { - private final BitmapPool bitmapPool; +import code.name.monkey.retromusic.util.RetroColorUtil; - public BitmapPaletteTranscoder(Context context) { - this(Glide.get(context).getBitmapPool()); - } +public class BitmapPaletteTranscoder implements ResourceTranscoder { - public BitmapPaletteTranscoder(BitmapPool bitmapPool) { - this.bitmapPool = bitmapPool; - } - - @Override - public Resource transcode(Resource bitmapResource) { - Bitmap bitmap = bitmapResource.get(); + @Override + public Resource transcode(@NonNull Resource toTranscode, @NonNull Options options) { + Bitmap bitmap = toTranscode.get(); BitmapPaletteWrapper bitmapPaletteWrapper = - new BitmapPaletteWrapper(bitmap, RetroColorUtil.generatePalette(bitmap)); - return new BitmapPaletteResource(bitmapPaletteWrapper, bitmapPool); - } - - @Override - public String getId() { - return "BitmapPaletteTranscoder.com.kabouzeid.gramophone.glide.palette"; + new BitmapPaletteWrapper(bitmap, RetroColorUtil.generatePalette(bitmap)); + return new BitmapPaletteResource(bitmapPaletteWrapper); } } diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteWrapper.java b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteWrapper.java index df713937..db647f6e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteWrapper.java +++ b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteWrapper.java @@ -15,6 +15,7 @@ package code.name.monkey.retromusic.glide.palette; import android.graphics.Bitmap; + import androidx.palette.graphics.Palette; public class BitmapPaletteWrapper { diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/playlistPreview/PlaylistPreview.kt b/app/src/main/java/code/name/monkey/retromusic/glide/playlistPreview/PlaylistPreview.kt new file mode 100644 index 00000000..b9257db6 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/glide/playlistPreview/PlaylistPreview.kt @@ -0,0 +1,31 @@ +package code.name.monkey.retromusic.glide.playlistPreview + +import code.name.monkey.retromusic.db.PlaylistEntity +import code.name.monkey.retromusic.db.PlaylistWithSongs +import code.name.monkey.retromusic.db.toSongs +import code.name.monkey.retromusic.model.Song + +class PlaylistPreview(val playlistWithSongs: PlaylistWithSongs) { + + val playlistEntity: PlaylistEntity get() = playlistWithSongs.playlistEntity + val songs: List get() = playlistWithSongs.songs.toSongs() + + override fun equals(other: Any?): Boolean { + if (other is PlaylistPreview) { + if (other.playlistEntity.playListId != playlistEntity.playListId) { + return false + } + if (other.songs.size != songs.size) { + return false + } + return true + } + return false + } + + override fun hashCode(): Int { + var result = playlistEntity.playListId.hashCode() + result = 31 * result + playlistWithSongs.songs.size + return result + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/playlistPreview/PlaylistPreviewFetcher.kt b/app/src/main/java/code/name/monkey/retromusic/glide/playlistPreview/PlaylistPreviewFetcher.kt new file mode 100644 index 00000000..0b18f6e0 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/glide/playlistPreview/PlaylistPreviewFetcher.kt @@ -0,0 +1,51 @@ +package code.name.monkey.retromusic.glide.playlistPreview + +import android.content.Context +import android.graphics.Bitmap +import code.name.monkey.retromusic.util.AutoGeneratedPlaylistBitmap +import com.bumptech.glide.Priority +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.data.DataFetcher +import kotlinx.coroutines.* +import java.util.concurrent.Executors + +class PlaylistPreviewFetcher(val context: Context, private val playlistPreview: PlaylistPreview) : + DataFetcher, CoroutineScope by GlideScope() { + override fun loadData(priority: Priority, callback: DataFetcher.DataCallback) { + launch { + try { + val bitmap = + AutoGeneratedPlaylistBitmap.getBitmap( + context, + playlistPreview.songs.shuffled(), + true, + false + ) + callback.onDataReady(bitmap) + } catch (e: Exception) { + callback.onLoadFailed(e) + } + } + } + + override fun cleanup() {} + + override fun cancel() { + cancel(null) + } + + override fun getDataClass(): Class { + return Bitmap::class.java + } + + override fun getDataSource(): DataSource { + return DataSource.LOCAL + } +} + +private val glideDispatcher: CoroutineDispatcher by lazy { + Executors.newFixedThreadPool(4).asCoroutineDispatcher() +} + +@Suppress("FunctionName") +internal fun GlideScope(): CoroutineScope = CoroutineScope(SupervisorJob() + glideDispatcher) \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/playlistPreview/PlaylistPreviewLoader.kt b/app/src/main/java/code/name/monkey/retromusic/glide/playlistPreview/PlaylistPreviewLoader.kt new file mode 100644 index 00000000..6dae74c9 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/glide/playlistPreview/PlaylistPreviewLoader.kt @@ -0,0 +1,36 @@ +package code.name.monkey.retromusic.glide.playlistPreview + +import android.content.Context +import android.graphics.Bitmap +import com.bumptech.glide.load.Options +import com.bumptech.glide.load.model.ModelLoader +import com.bumptech.glide.load.model.ModelLoader.LoadData +import com.bumptech.glide.load.model.ModelLoaderFactory +import com.bumptech.glide.load.model.MultiModelLoaderFactory +import com.bumptech.glide.signature.ObjectKey + +class PlaylistPreviewLoader(val context: Context) : ModelLoader { + override fun buildLoadData( + model: PlaylistPreview, + width: Int, + height: Int, + options: Options + ): LoadData { + return LoadData( + ObjectKey(model), + PlaylistPreviewFetcher(context, model) + ) + } + + override fun handles(model: PlaylistPreview): Boolean { + return true + } + + class Factory(val context: Context) : ModelLoaderFactory { + override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader { + return PlaylistPreviewLoader(context) + } + + override fun teardown() {} + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/MusicPlayerRemote.kt b/app/src/main/java/code/name/monkey/retromusic/helper/MusicPlayerRemote.kt index fc815fe0..4f7b5c93 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/MusicPlayerRemote.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/MusicPlayerRemote.kt @@ -29,10 +29,11 @@ import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.repository.SongRepository import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.util.PreferenceUtil -import java.io.File -import java.util.* import org.koin.core.KoinComponent import org.koin.core.inject +import java.io.File +import java.util.* + object MusicPlayerRemote : KoinComponent { val TAG: String = MusicPlayerRemote::class.java.simpleName @@ -41,6 +42,15 @@ object MusicPlayerRemote : KoinComponent { private val songRepository by inject() + var isCasting: Boolean = false + set(value) { + field = value + if (value) { + musicService?.quit() + } + println(value.toString() + "" + isCasting.toString()) + } + @JvmStatic val isPlaying: Boolean get() = musicService != null && musicService!!.isPlaying @@ -56,6 +66,11 @@ object MusicPlayerRemote : KoinComponent { musicService!!.currentSong } else Song.emptySong + val nextSong: Song? + get() = if (musicService != null) { + musicService?.nextSong + } else Song.emptySong + /** * Async */ @@ -73,7 +88,7 @@ object MusicPlayerRemote : KoinComponent { val playingQueue: List get() = if (musicService != null) { musicService?.playingQueue as List - } else listOf() + } else listOf() val songProgressMillis: Int get() = if (musicService != null) { @@ -295,7 +310,7 @@ object MusicPlayerRemote : KoinComponent { fun playNext(song: Song): Boolean { if (musicService != null) { - if (playingQueue.size > 0) { + if (playingQueue.isNotEmpty()) { musicService?.addSong(position + 1, song) } else { val queue = ArrayList() @@ -314,7 +329,7 @@ object MusicPlayerRemote : KoinComponent { fun playNext(songs: List): Boolean { if (musicService != null) { - if (playingQueue.size > 0) { + if (playingQueue.isNotEmpty()) { musicService?.addSongs(position + 1, songs) } else { openQueue(songs, 0, false) @@ -332,7 +347,7 @@ object MusicPlayerRemote : KoinComponent { fun enqueue(song: Song): Boolean { if (musicService != null) { - if (playingQueue.size > 0) { + if (playingQueue.isNotEmpty()) { musicService?.addSong(song) } else { val queue = ArrayList() @@ -351,7 +366,7 @@ object MusicPlayerRemote : KoinComponent { fun enqueue(songs: List): Boolean { if (musicService != null) { - if (playingQueue.size > 0) { + if (playingQueue.isNotEmpty()) { musicService?.addSongs(songs) } else { openQueue(songs, 0, false) diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/SearchQueryHelper.kt b/app/src/main/java/code/name/monkey/retromusic/helper/SearchQueryHelper.kt index 936af2b9..36e9728a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/SearchQueryHelper.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/SearchQueryHelper.kt @@ -19,9 +19,9 @@ import android.os.Bundle import android.provider.MediaStore import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.repository.RealSongRepository -import java.util.* import org.koin.core.KoinComponent import org.koin.core.inject +import java.util.* object SearchQueryHelper : KoinComponent { private const val TITLE_SELECTION = "lower(" + MediaStore.Audio.AudioColumns.TITLE + ") = ?" @@ -44,9 +44,9 @@ object SearchQueryHelper : KoinComponent { songRepository.makeSongCursor( ARTIST_SELECTION + AND + ALBUM_SELECTION + AND + TITLE_SELECTION, arrayOf( - artistName.toLowerCase(Locale.getDefault()), - albumName.toLowerCase(Locale.getDefault()), - titleName.toLowerCase(Locale.getDefault()) + artistName.lowercase(), + albumName.lowercase(), + titleName.lowercase() ) ) ) @@ -59,8 +59,8 @@ object SearchQueryHelper : KoinComponent { songRepository.makeSongCursor( ARTIST_SELECTION + AND + TITLE_SELECTION, arrayOf( - artistName.toLowerCase(Locale.getDefault()), - titleName.toLowerCase(Locale.getDefault()) + artistName.lowercase(), + titleName.lowercase() ) ) ) @@ -73,8 +73,8 @@ object SearchQueryHelper : KoinComponent { songRepository.makeSongCursor( ALBUM_SELECTION + AND + TITLE_SELECTION, arrayOf( - albumName.toLowerCase(Locale.getDefault()), - titleName.toLowerCase(Locale.getDefault()) + albumName.lowercase(), + titleName.lowercase() ) ) ) @@ -86,7 +86,7 @@ object SearchQueryHelper : KoinComponent { songs = songRepository.songs( songRepository.makeSongCursor( ARTIST_SELECTION, - arrayOf(artistName.toLowerCase(Locale.getDefault())) + arrayOf(artistName.lowercase()) ) ) } @@ -97,7 +97,7 @@ object SearchQueryHelper : KoinComponent { songs = songRepository.songs( songRepository.makeSongCursor( ALBUM_SELECTION, - arrayOf(albumName.toLowerCase(Locale.getDefault())) + arrayOf(albumName.lowercase()) ) ) } @@ -108,7 +108,7 @@ object SearchQueryHelper : KoinComponent { songs = songRepository.songs( songRepository.makeSongCursor( TITLE_SELECTION, - arrayOf(titleName.toLowerCase(Locale.getDefault())) + arrayOf(titleName.lowercase()) ) ) } @@ -118,7 +118,7 @@ object SearchQueryHelper : KoinComponent { songs = songRepository.songs( songRepository.makeSongCursor( ARTIST_SELECTION, - arrayOf(query.toLowerCase(Locale.getDefault())) + arrayOf(query.lowercase()) ) ) @@ -128,7 +128,7 @@ object SearchQueryHelper : KoinComponent { songs = songRepository.songs( songRepository.makeSongCursor( ALBUM_SELECTION, - arrayOf(query.toLowerCase(Locale.getDefault())) + arrayOf(query.lowercase()) ) ) if (songs.isNotEmpty()) { @@ -137,7 +137,7 @@ object SearchQueryHelper : KoinComponent { songs = songRepository.songs( songRepository.makeSongCursor( TITLE_SELECTION, - arrayOf(query.toLowerCase(Locale.getDefault())) + arrayOf(query.lowercase()) ) ) return if (songs.isNotEmpty()) { diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/SortOrder.kt b/app/src/main/java/code/name/monkey/retromusic/helper/SortOrder.kt index 22f92a09..7c7d68e2 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/SortOrder.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/SortOrder.kt @@ -15,6 +15,7 @@ package code.name.monkey.retromusic.helper import android.provider.MediaStore +import code.name.monkey.retromusic.ALBUM_ARTIST class SortOrder { @@ -53,7 +54,7 @@ class SortOrder { const val ALBUM_Z_A = "$ALBUM_A_Z DESC" /* Album sort order songs */ - const val ALBUM_NUMBER_OF_SONGS = MediaStore.Audio.Albums.NUMBER_OF_SONGS + " DESC" + const val ALBUM_NUMBER_OF_SONGS = MediaStore.Audio.AlbumColumns.NUMBER_OF_SONGS + " DESC" /* Album sort order artist */ const val ALBUM_ARTIST = (MediaStore.Audio.Artists.DEFAULT_SORT_ORDER + @@ -80,6 +81,9 @@ class SortOrder { /* Song sort order artist */ const val SONG_ARTIST = MediaStore.Audio.Artists.DEFAULT_SORT_ORDER + /* Song sort order album artist */ + const val SONG_ALBUM_ARTIST = ALBUM_ARTIST + /* Song sort order album */ const val SONG_ALBUM = MediaStore.Audio.Albums.DEFAULT_SORT_ORDER diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/StackBlur.java b/app/src/main/java/code/name/monkey/retromusic/helper/StackBlur.java index 9756fa13..91370fe8 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/StackBlur.java +++ b/app/src/main/java/code/name/monkey/retromusic/helper/StackBlur.java @@ -1,6 +1,7 @@ package code.name.monkey.retromusic.helper; import android.graphics.Bitmap; + import java.util.ArrayList; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongMenuHelper.kt b/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongMenuHelper.kt index 76a6b585..114a2ec2 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongMenuHelper.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongMenuHelper.kt @@ -21,6 +21,7 @@ import android.widget.PopupMenu import androidx.core.os.bundleOf import androidx.fragment.app.FragmentActivity import androidx.navigation.findNavController +import code.name.monkey.retromusic.App import code.name.monkey.retromusic.EXTRA_ALBUM_ID import code.name.monkey.retromusic.EXTRA_ARTIST_ID import code.name.monkey.retromusic.R @@ -29,9 +30,12 @@ import code.name.monkey.retromusic.activities.tageditor.SongTagEditorActivity import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog import code.name.monkey.retromusic.dialogs.DeleteSongsDialog import code.name.monkey.retromusic.dialogs.SongDetailDialog +import code.name.monkey.retromusic.fragments.LibraryViewModel +import code.name.monkey.retromusic.fragments.ReloadType import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.interfaces.IPaletteColorHolder import code.name.monkey.retromusic.model.Song +import code.name.monkey.retromusic.providers.BlacklistStore import code.name.monkey.retromusic.repository.RealRepository import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.RingtoneManager @@ -39,13 +43,16 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.koin.androidx.viewmodel.ext.android.getViewModel import org.koin.core.KoinComponent import org.koin.core.get +import java.io.File object SongMenuHelper : KoinComponent { const val MENU_RES = R.menu.menu_item_song fun handleMenuClick(activity: FragmentActivity, song: Song, menuItemId: Int): Boolean { + val libraryViewModel = activity.getViewModel() as LibraryViewModel when (menuItemId) { R.id.action_set_as_ringtone -> { if (RingtoneManager.requiresDialog(activity)) { @@ -116,6 +123,11 @@ object SongMenuHelper : KoinComponent { ) return true } + R.id.action_add_to_blacklist -> { + BlacklistStore.getInstance(App.getContext()).addPath(File(song.data)) + libraryViewModel.forceReload(ReloadType.Songs) + return true + } } return false } diff --git a/app/src/main/java/code/name/monkey/retromusic/interfaces/IAlbumArtistClickListener.kt b/app/src/main/java/code/name/monkey/retromusic/interfaces/IAlbumArtistClickListener.kt new file mode 100644 index 00000000..4def2ad3 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/interfaces/IAlbumArtistClickListener.kt @@ -0,0 +1,21 @@ +/* + * 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.interfaces + +import android.view.View + +interface IAlbumArtistClickListener { + fun onAlbumArtist(artistName: String, view: View) +} diff --git a/app/src/main/java/code/name/monkey/retromusic/interfaces/IMusicServiceEventListener.kt b/app/src/main/java/code/name/monkey/retromusic/interfaces/IMusicServiceEventListener.kt index 29669a3a..4d399180 100644 --- a/app/src/main/java/code/name/monkey/retromusic/interfaces/IMusicServiceEventListener.kt +++ b/app/src/main/java/code/name/monkey/retromusic/interfaces/IMusicServiceEventListener.kt @@ -21,6 +21,8 @@ interface IMusicServiceEventListener { fun onQueueChanged() + fun onFavoriteStateChanged() + fun onPlayingMetaChanged() fun onPlayStateChanged() diff --git a/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcHelper.java b/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcHelper.java index d1b71467..9770a9f4 100644 --- a/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcHelper.java +++ b/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcHelper.java @@ -1,6 +1,7 @@ package code.name.monkey.retromusic.lyrics; import android.content.Context; + import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; diff --git a/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcUtils.java b/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcUtils.java index a54c6c2e..b81fc782 100644 --- a/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcUtils.java +++ b/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcUtils.java @@ -17,6 +17,7 @@ package code.name.monkey.retromusic.lyrics; import android.animation.ValueAnimator; import android.text.TextUtils; import android.text.format.DateUtils; + import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; diff --git a/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcView.java b/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcView.java index 92eec91f..a27f1a19 100644 --- a/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcView.java +++ b/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcView.java @@ -34,13 +34,15 @@ import android.view.MotionEvent; import android.view.View; import android.view.animation.LinearInterpolator; import android.widget.Scroller; -import code.name.monkey.retromusic.BuildConfig; -import code.name.monkey.retromusic.R; + import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import code.name.monkey.retromusic.BuildConfig; +import code.name.monkey.retromusic.R; + /** 歌词 Created by wcy on 2015/11/9. */ @SuppressLint("StaticFieldLeak") public class LrcView extends View { @@ -109,6 +111,7 @@ public class LrcView extends View { mOffset = Math.min(mOffset, getOffset(0)); mOffset = Math.max(mOffset, getOffset(mLrcEntryList.size() - 1)); invalidate(); + getParent().requestDisallowInterceptTouchEvent(true); return true; } return super.onScroll(e1, e2, distanceX, distanceY); diff --git a/app/src/main/java/code/name/monkey/retromusic/misc/CustomFragmentStatePagerAdapter.java b/app/src/main/java/code/name/monkey/retromusic/misc/CustomFragmentStatePagerAdapter.java index 2c40d3bd..b8ff09bf 100644 --- a/app/src/main/java/code/name/monkey/retromusic/misc/CustomFragmentStatePagerAdapter.java +++ b/app/src/main/java/code/name/monkey/retromusic/misc/CustomFragmentStatePagerAdapter.java @@ -19,12 +19,14 @@ import android.os.Parcelable; import android.util.Log; import android.view.View; import android.view.ViewGroup; + import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentPagerAdapter; import androidx.fragment.app.FragmentTransaction; import androidx.viewpager.widget.PagerAdapter; + import java.util.ArrayList; /** diff --git a/app/src/main/java/code/name/monkey/retromusic/misc/DialogAsyncTask.java b/app/src/main/java/code/name/monkey/retromusic/misc/DialogAsyncTask.java index d83cc162..38e41539 100644 --- a/app/src/main/java/code/name/monkey/retromusic/misc/DialogAsyncTask.java +++ b/app/src/main/java/code/name/monkey/retromusic/misc/DialogAsyncTask.java @@ -17,8 +17,10 @@ package code.name.monkey.retromusic.misc; import android.app.Dialog; import android.content.Context; import android.os.Handler; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; + import java.lang.ref.WeakReference; public abstract class DialogAsyncTask diff --git a/app/src/main/java/code/name/monkey/retromusic/misc/LagTracker.java b/app/src/main/java/code/name/monkey/retromusic/misc/LagTracker.java index 402e2b38..98308118 100755 --- a/app/src/main/java/code/name/monkey/retromusic/misc/LagTracker.java +++ b/app/src/main/java/code/name/monkey/retromusic/misc/LagTracker.java @@ -15,6 +15,7 @@ package code.name.monkey.retromusic.misc; import android.util.Log; + import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -25,7 +26,7 @@ public class LagTracker { private boolean mEnabled = true; private LagTracker() { - mMap = new HashMap(); + mMap = new HashMap<>(); } public static LagTracker get() { @@ -64,7 +65,7 @@ public class LagTracker { long nanoTime = System.nanoTime(); if (this.mEnabled) { if (mMap.containsKey(str)) { - print(str, nanoTime - mMap.get(str).longValue()); + print(str, nanoTime - mMap.get(str)); mMap.remove(str); return; } @@ -77,7 +78,7 @@ public class LagTracker { public void start(String str) { long nanoTime = System.nanoTime(); if (this.mEnabled) { - mMap.put(str, Long.valueOf(nanoTime)); + mMap.put(str, nanoTime); } else if (!mMap.isEmpty()) { mMap.clear(); } diff --git a/app/src/main/java/code/name/monkey/retromusic/model/Album.kt b/app/src/main/java/code/name/monkey/retromusic/model/Album.kt index d4369b33..c894955b 100644 --- a/app/src/main/java/code/name/monkey/retromusic/model/Album.kt +++ b/app/src/main/java/code/name/monkey/retromusic/model/Album.kt @@ -19,13 +19,13 @@ data class Album( val songs: List ) { - val title: String? + val title: String get() = safeGetFirstSong().albumName val artistId: Long get() = safeGetFirstSong().artistId - val artistName: String? + val artistName: String get() = safeGetFirstSong().artistName val year: Int diff --git a/app/src/main/java/code/name/monkey/retromusic/model/Artist.kt b/app/src/main/java/code/name/monkey/retromusic/model/Artist.kt index bb4fd0b1..12150c45 100644 --- a/app/src/main/java/code/name/monkey/retromusic/model/Artist.kt +++ b/app/src/main/java/code/name/monkey/retromusic/model/Artist.kt @@ -15,23 +15,34 @@ package code.name.monkey.retromusic.model import code.name.monkey.retromusic.util.MusicUtil -import code.name.monkey.retromusic.util.PreferenceUtil -import java.util.* data class Artist( val id: Long, - val albums: List + val albums: List, + val isAlbumArtist: Boolean = false ) { + constructor( + artistName: String, + albums: List, + isAlbumArtist: Boolean = false + ) : this(albums[0].artistId, albums, isAlbumArtist) { + name = artistName + } - val name: String + var name: String = "" + set(value) { + field = value + } get() { - val name = safeGetFirstAlbum().safeGetFirstSong().albumArtist - if (PreferenceUtil.albumArtistsOnly && MusicUtil.isVariousArtists(name)) { - return VARIOUS_ARTISTS_DISPLAY_NAME + val name = if (isAlbumArtist) getAlbumArtistName() + else getArtistName() + return when { + MusicUtil.isVariousArtists(name) -> + VARIOUS_ARTISTS_DISPLAY_NAME + MusicUtil.isArtistNameUnknown(name) -> + UNKNOWN_ARTIST_DISPLAY_NAME + else -> name!! } - return if (MusicUtil.isArtistNameUnknown(name)) { - UNKNOWN_ARTIST_DISPLAY_NAME - } else safeGetFirstAlbum().safeGetFirstSong().artistName } val songCount: Int @@ -53,10 +64,18 @@ data class Artist( return albums.firstOrNull() ?: Album.empty } + private fun getArtistName(): String { + return safeGetFirstAlbum().safeGetFirstSong().artistName + } + + private fun getAlbumArtistName(): String? { + return safeGetFirstAlbum().safeGetFirstSong().albumArtist + } + companion object { const val UNKNOWN_ARTIST_DISPLAY_NAME = "Unknown Artist" const val VARIOUS_ARTISTS_DISPLAY_NAME = "Various Artists" - const val VARIOUS_ARTISTS_ID : Long = -2 + const val VARIOUS_ARTISTS_ID: Long = -2 val empty = Artist(-1, emptyList()) } diff --git a/app/src/main/java/code/name/monkey/retromusic/model/CategoryInfo.kt b/app/src/main/java/code/name/monkey/retromusic/model/CategoryInfo.kt index e11eb050..cd62d4c8 100644 --- a/app/src/main/java/code/name/monkey/retromusic/model/CategoryInfo.kt +++ b/app/src/main/java/code/name/monkey/retromusic/model/CategoryInfo.kt @@ -17,7 +17,7 @@ import android.os.Parcelable import androidx.annotation.DrawableRes import androidx.annotation.StringRes import code.name.monkey.retromusic.R -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize data class CategoryInfo( @@ -31,12 +31,12 @@ data class CategoryInfo( @StringRes val stringRes: Int, @DrawableRes val icon: Int ) { - Home(R.id.action_home, R.string.for_you, R.drawable.ic_face), - Songs(R.id.action_song, R.string.songs, R.drawable.ic_audiotrack), - Albums(R.id.action_album, R.string.albums, R.drawable.ic_album), - Artists(R.id.action_artist, R.string.artists, R.drawable.ic_artist), - Playlists(R.id.action_playlist, R.string.playlists, R.drawable.ic_queue_music), - Genres(R.id.action_genre, R.string.genres, R.drawable.ic_guitar), - Folder(R.id.action_folder, R.string.folders, R.drawable.ic_folder); + Home(R.id.action_home, R.string.for_you, R.drawable.asld_face), + Songs(R.id.action_song, R.string.songs, R.drawable.asld_music_note), + Albums(R.id.action_album, R.string.albums, R.drawable.asld_album), + Artists(R.id.action_artist, R.string.artists, R.drawable.asld_artist), + Playlists(R.id.action_playlist, R.string.playlists, R.drawable.asld_playlist), + Genres(R.id.action_genre, R.string.genres, R.drawable.asld_guitar), + Folder(R.id.action_folder, R.string.folders, R.drawable.asld_folder); } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/model/Contributor.kt b/app/src/main/java/code/name/monkey/retromusic/model/Contributor.kt index 23f952a3..7789366f 100644 --- a/app/src/main/java/code/name/monkey/retromusic/model/Contributor.kt +++ b/app/src/main/java/code/name/monkey/retromusic/model/Contributor.kt @@ -16,7 +16,7 @@ package code.name.monkey.retromusic.model import android.os.Parcelable import com.google.gson.annotations.SerializedName -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize class Contributor( diff --git a/app/src/main/java/code/name/monkey/retromusic/model/Genre.kt b/app/src/main/java/code/name/monkey/retromusic/model/Genre.kt index 961f2ba1..782c2bcc 100644 --- a/app/src/main/java/code/name/monkey/retromusic/model/Genre.kt +++ b/app/src/main/java/code/name/monkey/retromusic/model/Genre.kt @@ -15,7 +15,7 @@ package code.name.monkey.retromusic.model import android.os.Parcelable -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize data class Genre( diff --git a/app/src/main/java/code/name/monkey/retromusic/model/Playlist.kt b/app/src/main/java/code/name/monkey/retromusic/model/Playlist.kt index 690f753b..ca2e7d77 100644 --- a/app/src/main/java/code/name/monkey/retromusic/model/Playlist.kt +++ b/app/src/main/java/code/name/monkey/retromusic/model/Playlist.kt @@ -4,7 +4,7 @@ import android.content.Context import android.os.Parcelable import code.name.monkey.retromusic.repository.RealPlaylistRepository import code.name.monkey.retromusic.util.MusicUtil -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize import org.koin.core.KoinComponent import org.koin.core.get diff --git a/app/src/main/java/code/name/monkey/retromusic/model/PlaylistSong.kt b/app/src/main/java/code/name/monkey/retromusic/model/PlaylistSong.kt index d972e318..ccbb31c2 100644 --- a/app/src/main/java/code/name/monkey/retromusic/model/PlaylistSong.kt +++ b/app/src/main/java/code/name/monkey/retromusic/model/PlaylistSong.kt @@ -13,7 +13,7 @@ */ package code.name.monkey.retromusic.model -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize /** * Created by hemanths on 3/4/19 diff --git a/app/src/main/java/code/name/monkey/retromusic/model/Song.kt b/app/src/main/java/code/name/monkey/retromusic/model/Song.kt index 8c1481e1..cfd61a93 100644 --- a/app/src/main/java/code/name/monkey/retromusic/model/Song.kt +++ b/app/src/main/java/code/name/monkey/retromusic/model/Song.kt @@ -14,9 +14,7 @@ package code.name.monkey.retromusic.model import android.os.Parcelable -import code.name.monkey.retromusic.db.HistoryEntity -import code.name.monkey.retromusic.db.SongEntity -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize // update equals and hashcode if fields changes @Parcelize diff --git a/app/src/main/java/code/name/monkey/retromusic/model/lyrics/Lyrics.java b/app/src/main/java/code/name/monkey/retromusic/model/lyrics/Lyrics.java index dad81ff6..c466949f 100644 --- a/app/src/main/java/code/name/monkey/retromusic/model/lyrics/Lyrics.java +++ b/app/src/main/java/code/name/monkey/retromusic/model/lyrics/Lyrics.java @@ -14,9 +14,10 @@ package code.name.monkey.retromusic.model.lyrics; -import code.name.monkey.retromusic.model.Song; import java.util.ArrayList; +import code.name.monkey.retromusic.model.Song; + public class Lyrics { private static final ArrayList> FORMATS = new ArrayList<>(); diff --git a/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/HistoryPlaylist.kt b/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/HistoryPlaylist.kt index f9152967..9506314c 100644 --- a/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/HistoryPlaylist.kt +++ b/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/HistoryPlaylist.kt @@ -3,7 +3,7 @@ package code.name.monkey.retromusic.model.smartplaylist import code.name.monkey.retromusic.App import code.name.monkey.retromusic.R import code.name.monkey.retromusic.model.Song -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize import org.koin.core.KoinComponent @Parcelize diff --git a/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/LastAddedPlaylist.kt b/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/LastAddedPlaylist.kt index 6b8dafd4..f3b7b1b1 100644 --- a/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/LastAddedPlaylist.kt +++ b/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/LastAddedPlaylist.kt @@ -3,7 +3,7 @@ package code.name.monkey.retromusic.model.smartplaylist import code.name.monkey.retromusic.App import code.name.monkey.retromusic.R import code.name.monkey.retromusic.model.Song -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize class LastAddedPlaylist : AbsSmartPlaylist( diff --git a/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/NotPlayedPlaylist.kt b/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/NotPlayedPlaylist.kt index d9cbd6af..f77565da 100644 --- a/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/NotPlayedPlaylist.kt +++ b/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/NotPlayedPlaylist.kt @@ -3,7 +3,7 @@ package code.name.monkey.retromusic.model.smartplaylist import code.name.monkey.retromusic.App import code.name.monkey.retromusic.R import code.name.monkey.retromusic.model.Song -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize class NotPlayedPlaylist : AbsSmartPlaylist( diff --git a/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/ShuffleAllPlaylist.kt b/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/ShuffleAllPlaylist.kt index 69372abe..35c07d71 100644 --- a/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/ShuffleAllPlaylist.kt +++ b/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/ShuffleAllPlaylist.kt @@ -3,7 +3,7 @@ package code.name.monkey.retromusic.model.smartplaylist import code.name.monkey.retromusic.App import code.name.monkey.retromusic.R import code.name.monkey.retromusic.model.Song -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize class ShuffleAllPlaylist : AbsSmartPlaylist( diff --git a/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/TopTracksPlaylist.kt b/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/TopTracksPlaylist.kt index 4348ce82..d33be020 100644 --- a/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/TopTracksPlaylist.kt +++ b/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/TopTracksPlaylist.kt @@ -3,7 +3,7 @@ package code.name.monkey.retromusic.model.smartplaylist import code.name.monkey.retromusic.App import code.name.monkey.retromusic.R import code.name.monkey.retromusic.model.Song -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize class TopTracksPlaylist : AbsSmartPlaylist( diff --git a/app/src/main/java/code/name/monkey/retromusic/network/RetrofitClient.kt b/app/src/main/java/code/name/monkey/retromusic/network/RetrofitClient.kt index 719d682f..fdda85c0 100644 --- a/app/src/main/java/code/name/monkey/retromusic/network/RetrofitClient.kt +++ b/app/src/main/java/code/name/monkey/retromusic/network/RetrofitClient.kt @@ -40,7 +40,7 @@ fun headerInterceptor(context: Context): Interceptor { val request = original.newBuilder() .header("User-Agent", context.packageName) .addHeader("Content-Type", "application/json; charset=utf-8") - .method(original.method(), original.body()) + .method(original.method, original.body) .build() it.proceed(request) } diff --git a/app/src/main/java/code/name/monkey/retromusic/network/conversion/LyricsConverterFactory.kt b/app/src/main/java/code/name/monkey/retromusic/network/conversion/LyricsConverterFactory.kt index 474e81c9..1103653c 100644 --- a/app/src/main/java/code/name/monkey/retromusic/network/conversion/LyricsConverterFactory.kt +++ b/app/src/main/java/code/name/monkey/retromusic/network/conversion/LyricsConverterFactory.kt @@ -14,8 +14,9 @@ */ package code.name.monkey.retromusic.network.conversion -import okhttp3.MediaType +import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.ResponseBody import retrofit2.Converter import retrofit2.Retrofit @@ -41,11 +42,11 @@ class LyricsConverterFactory : Converter.Factory() { ): Converter<*, RequestBody>? { return if (String::class.java == type) { - Converter { value -> RequestBody.create(MEDIA_TYPE, value) } + Converter { value -> value.toRequestBody(MEDIA_TYPE) } } else null } companion object { - private val MEDIA_TYPE = MediaType.parse("text/plain") + private val MEDIA_TYPE = "text/plain".toMediaTypeOrNull() } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/network/model/LastFmAlbum.java b/app/src/main/java/code/name/monkey/retromusic/network/model/LastFmAlbum.java index b912503c..481372ff 100644 --- a/app/src/main/java/code/name/monkey/retromusic/network/model/LastFmAlbum.java +++ b/app/src/main/java/code/name/monkey/retromusic/network/model/LastFmAlbum.java @@ -16,6 +16,7 @@ package code.name.monkey.retromusic.network.model; import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; + import java.util.ArrayList; import java.util.List; diff --git a/app/src/main/java/code/name/monkey/retromusic/network/model/LastFmArtist.java b/app/src/main/java/code/name/monkey/retromusic/network/model/LastFmArtist.java index 0f91b5e4..ca6a97fe 100644 --- a/app/src/main/java/code/name/monkey/retromusic/network/model/LastFmArtist.java +++ b/app/src/main/java/code/name/monkey/retromusic/network/model/LastFmArtist.java @@ -16,6 +16,7 @@ package code.name.monkey.retromusic.network.model; import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; + import java.util.ArrayList; import java.util.List; diff --git a/app/src/main/java/code/name/monkey/retromusic/network/model/LastFmTrack.java b/app/src/main/java/code/name/monkey/retromusic/network/model/LastFmTrack.java index ee8848a3..cdaed55e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/network/model/LastFmTrack.java +++ b/app/src/main/java/code/name/monkey/retromusic/network/model/LastFmTrack.java @@ -16,6 +16,7 @@ package code.name.monkey.retromusic.network.model; import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; + import java.util.List; /** Created by hemanths on 15/06/17. */ diff --git a/app/src/main/java/code/name/monkey/retromusic/preferences/AlbumCoverStylePreferenceDialog.kt b/app/src/main/java/code/name/monkey/retromusic/preferences/AlbumCoverStylePreferenceDialog.kt index 5c7e91ba..bdde24be 100644 --- a/app/src/main/java/code/name/monkey/retromusic/preferences/AlbumCoverStylePreferenceDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/preferences/AlbumCoverStylePreferenceDialog.kt @@ -70,7 +70,7 @@ class AlbumCoverStylePreferenceDialog : DialogFragment(), override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { @SuppressLint("InflateParams") val view = - LayoutInflater.from(requireContext()) + layoutInflater .inflate(R.layout.preference_dialog_now_playing_screen, null) val viewPager = view.findViewById(R.id.now_playing_screen_view_pager) viewPager.adapter = AlbumCoverStyleAdapter(requireContext()) diff --git a/app/src/main/java/code/name/monkey/retromusic/preferences/DurationPreference.kt b/app/src/main/java/code/name/monkey/retromusic/preferences/DurationPreference.kt new file mode 100644 index 00000000..c9b5e5d2 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/preferences/DurationPreference.kt @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2019 Hemanth Savarala. + * + * 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.preferences + +import android.app.Dialog +import android.content.Context +import android.content.res.ColorStateList +import android.os.Bundle +import android.util.AttributeSet +import android.widget.TextView +import androidx.core.graphics.BlendModeColorFilterCompat +import androidx.core.graphics.BlendModeCompat.SRC_IN +import androidx.fragment.app.DialogFragment +import code.name.monkey.appthemehelper.ThemeStore +import code.name.monkey.appthemehelper.common.prefs.supportv7.ATEDialogPreference +import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.extensions.colorButtons +import code.name.monkey.retromusic.extensions.colorControlNormal +import code.name.monkey.retromusic.extensions.materialDialog +import code.name.monkey.retromusic.util.PreferenceUtil +import com.google.android.material.slider.Slider + + +class DurationPreference @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + defStyleRes: Int = 0 +) : ATEDialogPreference(context, attrs, defStyleAttr, defStyleRes) { + init { + icon?.colorFilter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat( + context.colorControlNormal(), + SRC_IN + ) + } +} + +class DurationPreferenceDialog : DialogFragment() { + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val view = layoutInflater + .inflate(R.layout.preference_dialog_audio_fade, null) + + + val slider = view.findViewById(R.id.slider) + val duration = view.findViewById(R.id.duration) + ColorStateList.valueOf(ThemeStore.accentColor(requireContext())).let { + slider.trackTintList = it + slider.thumbTintList = it + } + slider.value = PreferenceUtil.audioFadeDuration.toFloat() + updateText(slider.value.toInt(), duration) + slider.addOnChangeListener(Slider.OnChangeListener { _, value, fromUser -> + if (fromUser) { + updateText(value.toInt(), duration) + } + }) + + + return materialDialog(R.string.audio_fade_duration) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.save) { _, _ -> updateDuration(slider.value.toInt()) } + .setView(view) + .create() + .colorButtons() + } + + private fun updateText(value: Int, duration: TextView) { + var durationText = "$value ms" + if (value == 0) durationText += " / Off" + duration.text = durationText + } + + private fun updateDuration(duration: Int) { + PreferenceUtil.audioFadeDuration = duration + } + + companion object { + fun newInstance(): DurationPreferenceDialog { + return DurationPreferenceDialog() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/preferences/LibraryPreference.kt b/app/src/main/java/code/name/monkey/retromusic/preferences/LibraryPreference.kt index ccf12b4b..1b5192b5 100644 --- a/app/src/main/java/code/name/monkey/retromusic/preferences/LibraryPreference.kt +++ b/app/src/main/java/code/name/monkey/retromusic/preferences/LibraryPreference.kt @@ -14,12 +14,10 @@ package code.name.monkey.retromusic.preferences -import android.annotation.SuppressLint import android.app.Dialog import android.content.Context import android.os.Bundle import android.util.AttributeSet -import android.view.LayoutInflater import android.widget.Toast import androidx.core.graphics.BlendModeColorFilterCompat import androidx.core.graphics.BlendModeCompat.SRC_IN @@ -52,9 +50,8 @@ class LibraryPreference @JvmOverloads constructor( class LibraryPreferenceDialog : DialogFragment() { - @SuppressLint("InflateParams") override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val view = LayoutInflater.from(requireContext()) + val view = layoutInflater .inflate(R.layout.preference_dialog_library_categories, null) val categoryAdapter = CategoryInfoAdapter() diff --git a/app/src/main/java/code/name/monkey/retromusic/preferences/NowPlayingScreenPreferenceDialog.kt b/app/src/main/java/code/name/monkey/retromusic/preferences/NowPlayingScreenPreferenceDialog.kt index e6760195..d53c8c1f 100644 --- a/app/src/main/java/code/name/monkey/retromusic/preferences/NowPlayingScreenPreferenceDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/preferences/NowPlayingScreenPreferenceDialog.kt @@ -76,7 +76,7 @@ class NowPlayingScreenPreferenceDialog : DialogFragment(), ViewPager.OnPageChang } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val view = LayoutInflater.from(requireContext()) + val view = layoutInflater .inflate(R.layout.preference_dialog_now_playing_screen, null) val viewPager = view.findViewById(R.id.now_playing_screen_view_pager) ?: throw IllegalStateException("Dialog view must contain a ViewPager with id 'now_playing_screen_view_pager'") diff --git a/app/src/main/java/code/name/monkey/retromusic/providers/BlacklistStore.java b/app/src/main/java/code/name/monkey/retromusic/providers/BlacklistStore.java index ee4486a4..bf274f66 100644 --- a/app/src/main/java/code/name/monkey/retromusic/providers/BlacklistStore.java +++ b/app/src/main/java/code/name/monkey/retromusic/providers/BlacklistStore.java @@ -23,12 +23,15 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.os.Environment; + import androidx.annotation.NonNull; -import code.name.monkey.retromusic.util.FileUtil; -import code.name.monkey.retromusic.util.PreferenceUtil; + import java.io.File; import java.util.ArrayList; +import code.name.monkey.retromusic.util.FileUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; + public class BlacklistStore extends SQLiteOpenHelper { public static final String DATABASE_NAME = "blacklist.db"; private static final int VERSION = 2; diff --git a/app/src/main/java/code/name/monkey/retromusic/providers/HistoryStore.java b/app/src/main/java/code/name/monkey/retromusic/providers/HistoryStore.java index 996bb57a..7a637204 100644 --- a/app/src/main/java/code/name/monkey/retromusic/providers/HistoryStore.java +++ b/app/src/main/java/code/name/monkey/retromusic/providers/HistoryStore.java @@ -19,6 +19,7 @@ import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; diff --git a/app/src/main/java/code/name/monkey/retromusic/providers/MusicPlaybackQueueStore.java b/app/src/main/java/code/name/monkey/retromusic/providers/MusicPlaybackQueueStore.java index 1d1934a6..6875083b 100644 --- a/app/src/main/java/code/name/monkey/retromusic/providers/MusicPlaybackQueueStore.java +++ b/app/src/main/java/code/name/monkey/retromusic/providers/MusicPlaybackQueueStore.java @@ -20,12 +20,15 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.provider.BaseColumns; import android.provider.MediaStore.Audio.AudioColumns; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; + +import java.util.List; + import code.name.monkey.retromusic.App; import code.name.monkey.retromusic.model.Song; import code.name.monkey.retromusic.repository.RealSongRepository; -import java.util.List; /** * @author Andrew Neal, modified for Phonograph by Karim Abou Zeid diff --git a/app/src/main/java/code/name/monkey/retromusic/providers/SongPlayCountStore.java b/app/src/main/java/code/name/monkey/retromusic/providers/SongPlayCountStore.java index c19903f9..fd74c907 100644 --- a/app/src/main/java/code/name/monkey/retromusic/providers/SongPlayCountStore.java +++ b/app/src/main/java/code/name/monkey/retromusic/providers/SongPlayCountStore.java @@ -21,6 +21,7 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.view.animation.AccelerateInterpolator; import android.view.animation.Interpolator; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; diff --git a/app/src/main/java/code/name/monkey/retromusic/repository/AlbumRepository.kt b/app/src/main/java/code/name/monkey/retromusic/repository/AlbumRepository.kt index 97b8206a..ce023206 100644 --- a/app/src/main/java/code/name/monkey/retromusic/repository/AlbumRepository.kt +++ b/app/src/main/java/code/name/monkey/retromusic/repository/AlbumRepository.kt @@ -19,8 +19,6 @@ import code.name.monkey.retromusic.helper.SortOrder import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.PreferenceUtil -import java.util.* -import kotlin.collections.ArrayList /** @@ -71,11 +69,16 @@ class RealAlbumRepository(private val songRepository: RealSongRepository) : return album } + // We don't need sorted list of songs (with sortAlbumSongs()) + // cuz we are just displaying Albums(Cover Arts) anyway and not songs fun splitIntoAlbums( songs: List ): List { - return songs.groupBy { it.albumId } - .map { sortAlbumSongs(Album(it.key, it.value)) } + return if (PreferenceUtil.albumSortOrder != SortOrder.AlbumSortOrder.ALBUM_NUMBER_OF_SONGS) songs.groupBy { it.albumId } + .map { Album(it.key, it.value) } + // We can't sort Album with the help of MediaStore so a hack + else songs.groupBy { it.albumId }.map { Album(it.key, it.value) } + .sortedByDescending { it.songCount } } private fun sortAlbumSongs(album: Album): Album { @@ -98,9 +101,10 @@ class RealAlbumRepository(private val songRepository: RealSongRepository) : } private fun getSongLoaderSortOrder(): String { - return PreferenceUtil.albumSortOrder + ", " + + var albumSortOrder = PreferenceUtil.albumSortOrder + if (albumSortOrder == SortOrder.AlbumSortOrder.ALBUM_NUMBER_OF_SONGS) + albumSortOrder = SortOrder.AlbumSortOrder.ALBUM_A_Z + return albumSortOrder + ", " + PreferenceUtil.albumSongSortOrder } - - } diff --git a/app/src/main/java/code/name/monkey/retromusic/repository/ArtistRepository.kt b/app/src/main/java/code/name/monkey/retromusic/repository/ArtistRepository.kt index f0fd932f..1fe4839d 100644 --- a/app/src/main/java/code/name/monkey/retromusic/repository/ArtistRepository.kt +++ b/app/src/main/java/code/name/monkey/retromusic/repository/ArtistRepository.kt @@ -15,6 +15,7 @@ package code.name.monkey.retromusic.repository import android.provider.MediaStore.Audio.AudioColumns +import code.name.monkey.retromusic.helper.SortOrder import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.util.PreferenceUtil @@ -24,9 +25,13 @@ interface ArtistRepository { fun albumArtists(): List + fun albumArtists(query: String): List + fun artists(query: String): List fun artist(artistId: Long): Artist + + fun albumArtist(artistName: String): Artist } class RealArtistRepository( @@ -39,6 +44,7 @@ class RealArtistRepository( PreferenceUtil.artistAlbumSortOrder + ", " + PreferenceUtil.artistSongSortOrder } + override fun artist(artistId: Long): Artist { if (artistId == Artist.VARIOUS_ARTISTS_ID) { // Get Various Artists @@ -49,7 +55,8 @@ class RealArtistRepository( getSongLoaderSortOrder() ) ) - val albums = albumRepository.splitIntoAlbums(songs).filter { it.albumArtist == Artist.VARIOUS_ARTISTS_DISPLAY_NAME } + val albums = albumRepository.splitIntoAlbums(songs) + .filter { it.albumArtist == Artist.VARIOUS_ARTISTS_DISPLAY_NAME } return Artist(Artist.VARIOUS_ARTISTS_ID, albums) } @@ -62,6 +69,32 @@ class RealArtistRepository( ) return Artist(artistId, albumRepository.splitIntoAlbums(songs)) } + + override fun albumArtist(artistName: String): Artist { + if (artistName == Artist.VARIOUS_ARTISTS_DISPLAY_NAME) { + // Get Various Artists + val songs = songRepository.songs( + songRepository.makeSongCursor( + null, + null, + getSongLoaderSortOrder() + ) + ) + val albums = albumRepository.splitIntoAlbums(songs) + .filter { it.albumArtist == Artist.VARIOUS_ARTISTS_DISPLAY_NAME } + return Artist(Artist.VARIOUS_ARTISTS_ID, albums, true) + } + + val songs = songRepository.songs( + songRepository.makeSongCursor( + "album_artist" + "=?", + arrayOf(artistName), + getSongLoaderSortOrder() + ) + ) + return Artist(artistName, albumRepository.splitIntoAlbums(songs), true) + } + override fun artists(): List { val songs = songRepository.songs( songRepository.makeSongCursor( @@ -80,7 +113,17 @@ class RealArtistRepository( getSongLoaderSortOrder() ) ) + return splitIntoAlbumArtists(albumRepository.splitIntoAlbums(songs)) + } + override fun albumArtists(query: String): List { + val songs = songRepository.songs( + songRepository.makeSongCursor( + "album_artist" + " LIKE ?", + arrayOf("%$query%"), + getSongLoaderSortOrder() + ) + ) return splitIntoAlbumArtists(albumRepository.splitIntoAlbums(songs)) } @@ -98,22 +141,30 @@ class RealArtistRepository( private fun splitIntoAlbumArtists(albums: List): List { return albums.groupBy { it.albumArtist } + .filter { + !it.key.isNullOrEmpty() + } .map { val currentAlbums = it.value if (currentAlbums.isNotEmpty()) { if (currentAlbums[0].albumArtist == Artist.VARIOUS_ARTISTS_DISPLAY_NAME) { - Artist(Artist.VARIOUS_ARTISTS_ID, currentAlbums) + Artist(Artist.VARIOUS_ARTISTS_ID, currentAlbums, true) } else { - Artist(currentAlbums[0].artistId, currentAlbums) + Artist(currentAlbums[0].artistId, currentAlbums, true) } } else { Artist.empty } + }.apply { + if (PreferenceUtil.artistSortOrder == SortOrder.ArtistSortOrder.ARTIST_A_Z) { + sortedBy { it.name.lowercase() } + } else { + sortedByDescending { it.name.lowercase() } + } } } - fun splitIntoArtists(albums: List): List { return albums.groupBy { it.artistId } .map { Artist(it.key, it.value) } diff --git a/app/src/main/java/code/name/monkey/retromusic/repository/GenreRepository.kt b/app/src/main/java/code/name/monkey/retromusic/repository/GenreRepository.kt index ca7c822a..2c096092 100644 --- a/app/src/main/java/code/name/monkey/retromusic/repository/GenreRepository.kt +++ b/app/src/main/java/code/name/monkey/retromusic/repository/GenreRepository.kt @@ -18,7 +18,6 @@ import android.content.ContentResolver import android.database.Cursor import android.net.Uri import android.provider.BaseColumns -import android.provider.MediaStore import android.provider.MediaStore.Audio.Genres import code.name.monkey.retromusic.Constants.IS_MUSIC import code.name.monkey.retromusic.Constants.baseProjection @@ -33,6 +32,8 @@ interface GenreRepository { fun genres(): List fun songs(genreId: Long): List + + fun song(genreId: Long): Song } class RealGenreRepository( @@ -52,6 +53,10 @@ class RealGenreRepository( } else songRepository.songs(makeGenreSongCursor(genreId)) } + override fun song(genreId: Long): Song { + return songRepository.song(makeGenreSongCursor(genreId)) + } + private fun getGenreFromCursor(cursor: Cursor): Genre { val id = cursor.getLong(Genres._ID) val name = cursor.getStringOrNull(Genres.NAME) @@ -87,7 +92,7 @@ class RealGenreRepository( } private fun makeAllSongsWithGenreCursor(): Cursor? { - println(MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI.toString()) + println(Genres.EXTERNAL_CONTENT_URI.toString()) return contentResolver.query( Uri.parse("content://media/external/audio/genres/all/members"), arrayOf(Genres.Members.AUDIO_ID), null, null, null diff --git a/app/src/main/java/code/name/monkey/retromusic/repository/Repository.kt b/app/src/main/java/code/name/monkey/retromusic/repository/Repository.kt index cbd29a90..7eaab7dd 100644 --- a/app/src/main/java/code/name/monkey/retromusic/repository/Repository.kt +++ b/app/src/main/java/code/name/monkey/retromusic/repository/Repository.kt @@ -26,6 +26,7 @@ import code.name.monkey.retromusic.network.Result import code.name.monkey.retromusic.network.Result.* import code.name.monkey.retromusic.network.model.LastFmAlbum import code.name.monkey.retromusic.network.model.LastFmArtist +import code.name.monkey.retromusic.util.PreferenceUtil import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -50,12 +51,13 @@ interface Repository { suspend fun albumArtists(): List suspend fun fetchLegacyPlaylist(): List suspend fun fetchGenres(): List - suspend fun search(query: String?): MutableList + suspend fun search(query: String?, filters: List): MutableList suspend fun getPlaylistSongs(playlist: Playlist): List suspend fun getGenre(genreId: Long): List suspend fun artistInfo(name: String, lang: String?, cache: String?): Result suspend fun albumInfo(artist: String, album: String): Result suspend fun artistById(artistId: Long): Artist + suspend fun albumArtistByName(name: String): Artist suspend fun recentArtists(): List suspend fun topArtists(): List suspend fun topAlbums(): List @@ -101,6 +103,7 @@ interface Repository { suspend fun searchArtists(query: String): List suspend fun searchSongs(query: String): List suspend fun searchAlbums(query: String): List + fun getSongByGenre(genreId: Long): Song } class RealRepository( @@ -127,6 +130,8 @@ class RealRepository( override suspend fun searchAlbums(query: String): List = albumRepository.albums(query) + override fun getSongByGenre(genreId: Long): Song = genreRepository.song(genreId) + override suspend fun searchArtists(query: String): List = artistRepository.artists(query) @@ -142,6 +147,8 @@ class RealRepository( override suspend fun artistById(artistId: Long): Artist = artistRepository.artist(artistId) + override suspend fun albumArtistByName(name: String): Artist = artistRepository.albumArtist(name) + override suspend fun recentArtists(): List = lastAddedRepository.recentArtists() override suspend fun recentAlbums(): List = lastAddedRepository.recentAlbums() @@ -156,8 +163,8 @@ class RealRepository( override suspend fun allSongs(): List = songRepository.songs() - override suspend fun search(query: String?): MutableList = - searchRepository.searchAll(context, query) + override suspend fun search(query: String?, filters: List): MutableList = + searchRepository.searchAll(context, query, filters) override suspend fun getPlaylistSongs(playlist: Playlist): List = if (playlist is AbsCustomPlaylist) { @@ -234,7 +241,9 @@ class RealRepository( ) for (section in sections) { if (section.arrayList.isNotEmpty()) { - homeSections.add(section) + if (section.homeSection != SUGGESTIONS || PreferenceUtil.homeSuggestions) { + homeSections.add(section) + } } } return homeSections @@ -332,12 +341,25 @@ class RealRepository( override fun favorites(): LiveData> = roomRepository.favoritePlaylistLiveData(context.getString(R.string.favorites)) + var suggestions = Home( + listOf(), SUGGESTIONS, + R.string.suggestion_songs + ) + override suspend fun suggestionsHome(): Home { - val songs = - NotPlayedPlaylist().songs().shuffled().takeIf { + if (!PreferenceUtil.homeSuggestions) return Home( + listOf(), + SUGGESTIONS, + R.string.suggestion_songs + ) + // Don't reload Suggestions everytime + if (suggestions.arrayList.isEmpty()) { + val songs = NotPlayedPlaylist().songs().shuffled().takeIf { it.size > 9 } ?: emptyList() - return Home(songs, SUGGESTIONS, R.string.suggestion_songs) + suggestions = Home(songs, SUGGESTIONS, R.string.suggestion_songs) + } + return suggestions } override suspend fun genresHome(): Home { diff --git a/app/src/main/java/code/name/monkey/retromusic/repository/RoomRepository.kt b/app/src/main/java/code/name/monkey/retromusic/repository/RoomRepository.kt index 8c6dd865..608a45fd 100644 --- a/app/src/main/java/code/name/monkey/retromusic/repository/RoomRepository.kt +++ b/app/src/main/java/code/name/monkey/retromusic/repository/RoomRepository.kt @@ -2,18 +2,7 @@ package code.name.monkey.retromusic.repository import androidx.annotation.WorkerThread import androidx.lifecycle.LiveData -import code.name.monkey.retromusic.db.BlackListStoreDao -import code.name.monkey.retromusic.db.BlackListStoreEntity -import code.name.monkey.retromusic.db.HistoryDao -import code.name.monkey.retromusic.db.HistoryEntity -import code.name.monkey.retromusic.db.LyricsDao -import code.name.monkey.retromusic.db.PlayCountDao -import code.name.monkey.retromusic.db.PlayCountEntity -import code.name.monkey.retromusic.db.PlaylistDao -import code.name.monkey.retromusic.db.PlaylistEntity -import code.name.monkey.retromusic.db.PlaylistWithSongs -import code.name.monkey.retromusic.db.SongEntity -import code.name.monkey.retromusic.db.toHistoryEntity +import code.name.monkey.retromusic.db.* import code.name.monkey.retromusic.helper.SortOrder.PlaylistSortOrder.Companion.PLAYLIST_A_Z import code.name.monkey.retromusic.helper.SortOrder.PlaylistSortOrder.Companion.PLAYLIST_SONG_COUNT import code.name.monkey.retromusic.helper.SortOrder.PlaylistSortOrder.Companion.PLAYLIST_SONG_COUNT_DESC diff --git a/app/src/main/java/code/name/monkey/retromusic/repository/SearchRepository.kt b/app/src/main/java/code/name/monkey/retromusic/repository/SearchRepository.kt index d8bb7b06..68305890 100644 --- a/app/src/main/java/code/name/monkey/retromusic/repository/SearchRepository.kt +++ b/app/src/main/java/code/name/monkey/retromusic/repository/SearchRepository.kt @@ -16,8 +16,10 @@ package code.name.monkey.retromusic.repository import android.content.Context import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.model.Album +import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.model.Genre -import java.util.* +import code.name.monkey.retromusic.model.Song class RealSearchRepository( private val songRepository: SongRepository, @@ -26,28 +28,54 @@ class RealSearchRepository( private val roomRepository: RoomRepository, private val genreRepository: GenreRepository, ) { - fun searchAll(context: Context, query: String?): MutableList { + fun searchAll(context: Context, query: String?, filters: List): MutableList { val results = mutableListOf() query?.let { searchString -> - val songs = songRepository.songs(searchString) + val isAll = !filters.contains(true) + val songs: List = if (filters[0] || isAll) { + songRepository.songs(searchString) + } else { + emptyList() + } + if (songs.isNotEmpty()) { results.add(context.resources.getString(R.string.songs)) results.addAll(songs) } - val artists = artistRepository.artists(searchString) + val artists: List = if (filters[1] || isAll) { + artistRepository.artists(searchString) + } else { + emptyList() + } if (artists.isNotEmpty()) { results.add(context.resources.getString(R.string.artists)) results.addAll(artists) } - - val albums = albumRepository.albums(searchString) + val albums: List = if (filters[2] || isAll) { + albumRepository.albums(searchString) + } else { + emptyList() + } if (albums.isNotEmpty()) { results.add(context.resources.getString(R.string.albums)) results.addAll(albums) } - val genres: List = genreRepository.genres().filter { genre -> - genre.name.toLowerCase(Locale.getDefault()) - .contains(searchString.toLowerCase(Locale.getDefault())) + val albumArtists: List = if (filters[3] || isAll) { + artistRepository.albumArtists(searchString) + } else { + emptyList() + } + if (albumArtists.isNotEmpty()) { + results.add(context.resources.getString(R.string.album_artist)) + results.addAll(albumArtists) + } + val genres: List = if (filters[4] || isAll) { + genreRepository.genres().filter { genre -> + genre.name.lowercase() + .contains(searchString.lowercase()) + } + } else { + emptyList() } if (genres.isNotEmpty()) { results.add(context.resources.getString(R.string.genres)) diff --git a/app/src/main/java/code/name/monkey/retromusic/repository/SongRepository.kt b/app/src/main/java/code/name/monkey/retromusic/repository/SongRepository.kt index da52da28..6a932ab2 100644 --- a/app/src/main/java/code/name/monkey/retromusic/repository/SongRepository.kt +++ b/app/src/main/java/code/name/monkey/retromusic/repository/SongRepository.kt @@ -16,6 +16,7 @@ package code.name.monkey.retromusic.repository import android.content.Context import android.database.Cursor +import android.os.Environment import android.provider.MediaStore import android.provider.MediaStore.Audio.AudioColumns import android.provider.MediaStore.Audio.Media @@ -140,12 +141,24 @@ class RealSongRepository(private val context: Context) : SongRepository { IS_MUSIC } - // Blacklist - val paths = BlacklistStore.getInstance(context).paths - if (paths.isNotEmpty()) { - selectionFinal = generateBlacklistSelection(selectionFinal, paths.size) - selectionValuesFinal = addBlacklistSelectionValues(selectionValuesFinal, paths) + // Whitelist + if (PreferenceUtil.isWhiteList) { + selectionFinal = + selectionFinal + " AND " + AudioColumns.DATA + " LIKE ?" + selectionValuesFinal = addSelectionValues( + selectionValuesFinal, arrayListOf( + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).canonicalPath + ) + ) + } else { + // Blacklist + val paths = BlacklistStore.getInstance(context).paths + if (paths.isNotEmpty()) { + selectionFinal = generateBlacklistSelection(selectionFinal, paths.size) + selectionValuesFinal = addSelectionValues(selectionValuesFinal, paths) + } } + selectionFinal = selectionFinal + " AND " + Media.DURATION + ">= " + (PreferenceUtil.filterLength * 1000) @@ -180,10 +193,10 @@ class RealSongRepository(private val context: Context) : SongRepository { return newSelection.toString() } - private fun addBlacklistSelectionValues( + private fun addSelectionValues( selectionValues: Array?, paths: ArrayList - ): Array? { + ): Array { var selectionValuesFinal = selectionValues if (selectionValuesFinal == null) { selectionValuesFinal = emptyArray() diff --git a/app/src/main/java/code/name/monkey/retromusic/repository/SortedCursor.java b/app/src/main/java/code/name/monkey/retromusic/repository/SortedCursor.java index f7705ca4..0d1cbb30 100644 --- a/app/src/main/java/code/name/monkey/retromusic/repository/SortedCursor.java +++ b/app/src/main/java/code/name/monkey/retromusic/repository/SortedCursor.java @@ -15,8 +15,10 @@ package code.name.monkey.retromusic.repository; import android.database.AbstractCursor; import android.database.Cursor; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; + import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; diff --git a/app/src/main/java/code/name/monkey/retromusic/repository/SortedLongCursor.java b/app/src/main/java/code/name/monkey/retromusic/repository/SortedLongCursor.java index 02ade994..9884a32e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/repository/SortedLongCursor.java +++ b/app/src/main/java/code/name/monkey/retromusic/repository/SortedLongCursor.java @@ -15,8 +15,10 @@ package code.name.monkey.retromusic.repository; import android.database.AbstractCursor; import android.database.Cursor; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; + import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; diff --git a/app/src/main/java/code/name/monkey/retromusic/service/AudioFader.kt b/app/src/main/java/code/name/monkey/retromusic/service/AudioFader.kt new file mode 100644 index 00000000..3f8c0605 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/service/AudioFader.kt @@ -0,0 +1,50 @@ +package code.name.monkey.retromusic.service + +import code.name.monkey.retromusic.service.playback.Playback +import java.util.* + +class AudioFader( + private val player: Playback, + durationMillis: Long, + private val fadeIn: Boolean, + private val doOnEnd: Runnable +) { + val timer = Timer() + var volume = if (fadeIn) 0F else 1F + val maxVolume = if (fadeIn) 1F else 0F + private val volumeStep: Float = PERIOD / durationMillis.toFloat() + + fun start() { + timer.scheduleAtFixedRate( + object : TimerTask() { + override fun run() { + setVolume() + if (volume < 0 || volume > 1) { + player.setVolume(maxVolume) + stop() + doOnEnd.run() + } else { + player.setVolume(volume) + } + } + }, 0, PERIOD + ) + } + + fun stop() { + timer.purge() + timer.cancel() + } + + private fun setVolume() { + if (fadeIn) { + volume += volumeStep + } else { + volume -= volumeStep + } + } + + companion object { + const val PERIOD = 100L + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/service/CrossFadePlayer.kt b/app/src/main/java/code/name/monkey/retromusic/service/CrossFadePlayer.kt new file mode 100644 index 00000000..6dda1e07 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/service/CrossFadePlayer.kt @@ -0,0 +1,406 @@ +package code.name.monkey.retromusic.service + +import android.animation.Animator +import android.animation.ValueAnimator +import android.content.Context +import android.content.Intent +import android.media.AudioAttributes +import android.media.AudioManager +import android.media.MediaPlayer +import android.media.audiofx.AudioEffect +import android.net.Uri +import android.os.Handler +import android.os.Message +import android.os.PowerManager +import android.widget.Toast +import androidx.core.animation.doOnEnd +import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.helper.MusicPlayerRemote +import code.name.monkey.retromusic.service.playback.Playback +import code.name.monkey.retromusic.service.playback.Playback.PlaybackCallbacks +import code.name.monkey.retromusic.util.MusicUtil +import code.name.monkey.retromusic.util.PreferenceUtil + +/** @author Prathamesh M */ + +/* +* To make Crossfade work we need two MediaPlayer's +* Basically, we switch back and forth between those two mp's +* e.g. When song is about to end (Reaches Crossfade duration) we let current mediaplayer +* play but with decreasing volume and start the player with the next song with increasing volume +* and vice versa for upcoming song and so on. +*/ +class CrossFadePlayer(val context: Context) : Playback, MediaPlayer.OnCompletionListener, + MediaPlayer.OnErrorListener { + + private var currentPlayer: CurrentPlayer = CurrentPlayer.NOT_SET + private var player1 = MediaPlayer() + private var player2 = MediaPlayer() + private var durationListener = DurationListener() + private var trackEndHandledByCrossFade = false + private var mIsInitialized = false + private var hasDataSource: Boolean = false /* Whether first player has DataSource */ + private var fadeInAnimator: Animator? = null + private var fadeOutAnimator: Animator? = null + private var callbacks: PlaybackCallbacks? = null + + init { + player1.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK) + player2.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK) + currentPlayer = CurrentPlayer.PLAYER_ONE + } + + override fun start(): Boolean { + durationListener.start() + return try { + getCurrentPlayer()?.start() + true + } catch (e: IllegalStateException) { + e.printStackTrace() + false + } + } + + override fun release() { + getCurrentPlayer()?.release() + getNextPlayer()?.release() + durationListener.stop() + } + + override fun setCallbacks(callbacks: PlaybackCallbacks) { + this.callbacks = callbacks + } + + override fun stop() { + getCurrentPlayer()?.reset() + mIsInitialized = false + } + + override fun pause(): Boolean { + durationListener.stop() + cancelFade() + getCurrentPlayer()?.let { + if (it.isPlaying) { + it.pause() + } + } + getNextPlayer()?.let { + if (it.isPlaying) { + it.pause() + } + } + return true + } + + override fun seek(whereto: Int): Int { + cancelFade() + getNextPlayer()?.stop() + return try { + getCurrentPlayer()?.seekTo(whereto) + whereto + } catch (e: java.lang.IllegalStateException) { + e.printStackTrace() + -1 + } + } + + override fun setVolume(vol: Float): Boolean { + cancelFade() + return try { + getCurrentPlayer()?.setVolume(vol, vol) + true + } catch (e: IllegalStateException) { + e.printStackTrace() + false + } + } + + override val isInitialized: Boolean + get() = mIsInitialized + + override val isPlaying: Boolean + get() = mIsInitialized && getCurrentPlayer()?.isPlaying == true + + // This has to run when queue is changed or song is changed manually by user + fun sourceChangedByUser() { + this.hasDataSource = false + cancelFade() + getCurrentPlayer()?.apply { + if (isPlaying) stop() + } + getNextPlayer()?.apply { + if (isPlaying) stop() + } + } + + override fun setDataSource(path: String): Boolean { + cancelFade() + mIsInitialized = false + /* We've already set DataSource if initialized is true in setNextDataSource */ + if (!hasDataSource) { + getCurrentPlayer()?.let { mIsInitialized = setDataSourceImpl(it, path) } + hasDataSource = true + } else { + mIsInitialized = true + } + return mIsInitialized + } + + override fun setNextDataSource(path: String?) {} + + /** + * @param player The {@link MediaPlayer} to use + * @param path The path of the file, or the http/rtsp URL of the stream you want to play + * @return True if the player has been prepared and is ready to play, false otherwise + */ + private fun setDataSourceImpl( + player: MediaPlayer, + path: String + ): Boolean { + player.reset() + player.setOnPreparedListener(null) + try { + if (path.startsWith("content://")) { + player.setDataSource(context, Uri.parse(path)) + } else { + player.setDataSource(path) + } + player.setAudioAttributes( + AudioAttributes.Builder().setLegacyStreamType(AudioManager.STREAM_MUSIC).build() + ) + player.prepare() + } catch (e: Exception) { + e.printStackTrace() + return false + } + player.setOnCompletionListener(this) + player.setOnErrorListener(this) + val intent = Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION) + intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, audioSessionId) + intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.packageName) + intent.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC) + context.sendBroadcast(intent) + return true + } + + override fun setAudioSessionId(sessionId: Int): Boolean { + return try { + getCurrentPlayer()?.audioSessionId = sessionId + true + } catch (e: IllegalArgumentException) { + e.printStackTrace() + false + } catch (e: IllegalStateException) { + e.printStackTrace() + false + } + } + + override val audioSessionId: Int + get() = getCurrentPlayer()?.audioSessionId!! + + /** + * Gets the duration of the file. + * + * @return The duration in milliseconds + */ + override fun duration(): Int { + return if (!mIsInitialized) { + -1 + } else try { + getCurrentPlayer()?.duration!! + } catch (e: IllegalStateException) { + e.printStackTrace() + -1 + } + } + + /** + * Gets the current position in audio. + * @return The position in milliseconds + */ + override fun position(): Int { + return if (!mIsInitialized) { + -1 + } else try { + getCurrentPlayer()?.currentPosition!! + } catch (e: IllegalStateException) { + e.printStackTrace() + -1 + } + } + + override fun onCompletion(mp: MediaPlayer?) { + if (mp == getNextPlayer()) { + if (trackEndHandledByCrossFade) { + trackEndHandledByCrossFade = false + } else { + notifyTrackEnded() + } + } + } + + private fun notifyTrackEnded(){ + if (callbacks != null) { + callbacks?.onTrackEnded() + } + } + + private fun getCurrentPlayer(): MediaPlayer? { + return when (currentPlayer) { + CurrentPlayer.PLAYER_ONE -> { + player1 + } + CurrentPlayer.PLAYER_TWO -> { + player2 + } + CurrentPlayer.NOT_SET -> { + null + } + } + } + + private fun getNextPlayer(): MediaPlayer? { + return when (currentPlayer) { + CurrentPlayer.PLAYER_ONE -> { + player2 + } + CurrentPlayer.PLAYER_TWO -> { + player1 + } + CurrentPlayer.NOT_SET -> { + null + } + } + } + + private fun fadeIn(mediaPlayer: MediaPlayer) { + fadeInAnimator = createFadeAnimator(true, mediaPlayer) { + println("Fade In Completed") + fadeInAnimator = null + } + fadeInAnimator?.start() + } + + private fun fadeOut(mediaPlayer: MediaPlayer) { + fadeOutAnimator = createFadeAnimator(false, mediaPlayer) { + println("Fade Out Completed") + fadeOutAnimator = null + } + fadeOutAnimator?.start() + } + + private fun cancelFade() { + fadeInAnimator?.cancel() + fadeOutAnimator?.cancel() + fadeInAnimator = null + fadeOutAnimator = null + getCurrentPlayer()?.setVolume(1f, 1f) + getNextPlayer()?.setVolume(0f, 0f) + } + + private fun createFadeAnimator( + fadeIn: Boolean /* fadeIn -> true fadeOut -> false*/, + mediaPlayer: MediaPlayer, + callback: Runnable /* Code to run when Animator Ends*/ + ): Animator? { + val duration = PreferenceUtil.crossFadeDuration * 1000 + if (duration == 0) { + return null + } + val startValue = if (fadeIn) 0f else 1.0f + val endValue = if (fadeIn) 1.0f else 0f + val animator = ValueAnimator.ofFloat(startValue, endValue) + animator.duration = duration.toLong() + animator.addUpdateListener { animation: ValueAnimator -> + mediaPlayer.setVolume( + animation.animatedValue as Float, animation.animatedValue as Float + ) + } + animator.doOnEnd { + callback.run() + // Set end values + mediaPlayer.setVolume(endValue, endValue) + } + return animator + } + + override fun onError(mp: MediaPlayer?, what: Int, extra: Int): Boolean { + mIsInitialized = false + mp?.release() + player1 = MediaPlayer() + player2 = MediaPlayer() + mIsInitialized = true + mp?.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK) + Toast.makeText( + context, + context.resources.getString(R.string.unplayable_file), + Toast.LENGTH_SHORT + ) + .show() + return false + } + + enum class CurrentPlayer { + PLAYER_ONE, + PLAYER_TWO, + NOT_SET + } + + inner class DurationListener : Handler() { + + fun start() { + nextRefresh() + } + + fun stop() { + removeMessages(DURATION_CHANGED) + } + + override fun handleMessage(msg: Message) { + super.handleMessage(msg) + if (msg.what == DURATION_CHANGED) { + nextRefresh() + onDurationUpdated(position(), duration()) + } + } + + private fun nextRefresh() { + val message = obtainMessage(DURATION_CHANGED) + removeMessages(DURATION_CHANGED) + sendMessageDelayed(message, 100) + } + } + + + fun onDurationUpdated(progress: Int, total: Int) { + if (total > 0 && (total - progress).div(1000) == PreferenceUtil.crossFadeDuration) { + getNextPlayer()?.let { player -> + val nextSong = MusicPlayerRemote.nextSong + if (nextSong != null) { + setDataSourceImpl(player, MusicUtil.getSongFileUri(nextSong.id).toString()) + // Switch to other player / Crossfade only if next song exists + switchPlayer() + } + } + } + } + + private fun switchPlayer() { + getNextPlayer()?.start() + getCurrentPlayer()?.let { fadeOut(it) } + getNextPlayer()?.let { fadeIn(it) } + currentPlayer = + if (currentPlayer == CurrentPlayer.PLAYER_ONE || currentPlayer == CurrentPlayer.NOT_SET) { + CurrentPlayer.PLAYER_TWO + } else { + CurrentPlayer.PLAYER_ONE + } + notifyTrackEnded() + trackEndHandledByCrossFade = true + } + + companion object { + private const val DURATION_CHANGED = 1 + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/service/MediaSessionCallback.kt b/app/src/main/java/code/name/monkey/retromusic/service/MediaSessionCallback.kt index 6a9bcb70..c901a391 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/MediaSessionCallback.kt +++ b/app/src/main/java/code/name/monkey/retromusic/service/MediaSessionCallback.kt @@ -18,11 +18,19 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.support.v4.media.session.MediaSessionCompat +import code.name.monkey.retromusic.auto.AutoMediaIDHelper import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote.cycleRepeatMode +import code.name.monkey.retromusic.helper.ShuffleHelper.makeShuffleList +import code.name.monkey.retromusic.model.Album +import code.name.monkey.retromusic.model.Artist +import code.name.monkey.retromusic.model.Playlist import code.name.monkey.retromusic.model.Song +import code.name.monkey.retromusic.repository.* import code.name.monkey.retromusic.service.MusicService.* import code.name.monkey.retromusic.util.MusicUtil +import org.koin.core.KoinComponent +import org.koin.core.inject import java.util.* @@ -33,7 +41,76 @@ import java.util.* class MediaSessionCallback( private val context: Context, private val musicService: MusicService -) : MediaSessionCompat.Callback() { +) : MediaSessionCompat.Callback(), KoinComponent { + + private val songRepository by inject() + private val albumRepository by inject() + private val artistRepository by inject() + private val genreRepository by inject() + private val playlistRepository by inject() + private val topPlayedRepository by inject() + + override fun onPlayFromMediaId(mediaId: String?, extras: Bundle?) { + super.onPlayFromMediaId(mediaId, extras) + val musicId = AutoMediaIDHelper.extractMusicID(mediaId!!) + println(musicId) + val itemId = musicId?.toLong() ?: -1 + val songs: ArrayList = ArrayList() + val category = AutoMediaIDHelper.extractCategory(mediaId) + when (category) { + AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM -> { + val album: Album = albumRepository.album(itemId) + songs.addAll(album.songs) + musicService.openQueue(songs, 0, true) + } + AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ARTIST -> { + val artist: Artist = artistRepository.artist(itemId) + songs.addAll(artist.songs) + musicService.openQueue(songs, 0, true) + } + AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM_ARTIST -> { + val artist: Artist = + artistRepository.albumArtist(albumRepository.album(itemId).albumArtist!!) + songs.addAll(artist.songs) + musicService.openQueue(songs, 0, true) + } + AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_PLAYLIST -> { + val playlist: Playlist = playlistRepository.playlist(itemId) + songs.addAll(playlist.getSongs()) + musicService.openQueue(songs, 0, true) + } + AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_GENRE -> { + songs.addAll(genreRepository.songs(itemId)) + musicService.openQueue(songs, 0, true) + } + AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_SHUFFLE -> { + val allSongs: ArrayList = songRepository.songs() as ArrayList + makeShuffleList(allSongs, -1) + musicService.openQueue(allSongs, 0, true) + } + AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_HISTORY, + AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_SUGGESTIONS, + AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_TOP_TRACKS, + AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_QUEUE -> { + val tracks: List = when (category) { + AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_HISTORY -> topPlayedRepository.recentlyPlayedTracks() + AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_SUGGESTIONS -> topPlayedRepository.recentlyPlayedTracks() + AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_TOP_TRACKS -> topPlayedRepository.recentlyPlayedTracks() + else -> musicService.playingQueue as List + } + songs.addAll(tracks) + var songIndex = MusicUtil.indexOfSongInList(tracks, itemId) + if (songIndex == -1) { + songIndex = 0 + } + musicService.openQueue(songs, songIndex, true) + } + else -> { + } + } + musicService.play() + } + override fun onPlay() { super.onPlay() diff --git a/app/src/main/java/code/name/monkey/retromusic/service/MultiPlayer.java b/app/src/main/java/code/name/monkey/retromusic/service/MultiPlayer.java index 9ed12f5e..c0d7e023 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/MultiPlayer.java +++ b/app/src/main/java/code/name/monkey/retromusic/service/MultiPlayer.java @@ -23,300 +23,302 @@ import android.net.Uri; import android.os.PowerManager; import android.util.Log; import android.widget.Toast; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; + import code.name.monkey.retromusic.R; import code.name.monkey.retromusic.service.playback.Playback; import code.name.monkey.retromusic.util.PreferenceUtil; /** @author Andrew Neal, Karim Abou Zeid (kabouzeid) */ public class MultiPlayer - implements Playback, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener { - public static final String TAG = MultiPlayer.class.getSimpleName(); + implements Playback, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener { + public static final String TAG = MultiPlayer.class.getSimpleName(); - private MediaPlayer mCurrentMediaPlayer = new MediaPlayer(); - private MediaPlayer mNextMediaPlayer; + private MediaPlayer mCurrentMediaPlayer = new MediaPlayer(); + private MediaPlayer mNextMediaPlayer; - private Context context; - @Nullable private Playback.PlaybackCallbacks callbacks; + private Context context; + @Nullable private Playback.PlaybackCallbacks callbacks; - private boolean mIsInitialized = false; + private boolean mIsInitialized = false; - /** Constructor of MultiPlayer */ - MultiPlayer(final Context context) { - this.context = context; - mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK); - } + /** Constructor of MultiPlayer */ + MultiPlayer(final Context context) { + this.context = context; + mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK); + } - /** - * @param path The path of the file, or the http/rtsp URL of the stream you want to play - * @return True if the player has been prepared and is ready to play, false otherwise - */ - @Override - public boolean setDataSource(@NonNull final String path) { - mIsInitialized = false; - mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path); - if (mIsInitialized) { - setNextDataSource(null); + /** + * @param path The path of the file, or the http/rtsp URL of the stream you want to play + * @return True if the player has been prepared and is ready to play, false otherwise + */ + @Override + public boolean setDataSource(@NonNull final String path) { + mIsInitialized = false; + mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path); + if (mIsInitialized) { + setNextDataSource(null); + } + return mIsInitialized; } - return mIsInitialized; - } - /** - * @param player The {@link MediaPlayer} to use - * @param path The path of the file, or the http/rtsp URL of the stream you want to play - * @return True if the player has been prepared and is ready to play, false otherwise - */ - private boolean setDataSourceImpl(@NonNull final MediaPlayer player, @NonNull final String path) { - if (context == null) { - return false; - } - try { - player.reset(); - player.setOnPreparedListener(null); - if (path.startsWith("content://")) { - player.setDataSource(context, Uri.parse(path)); - } else { - player.setDataSource(path); - } - player.setAudioStreamType(AudioManager.STREAM_MUSIC); - player.prepare(); - } catch (Exception e) { - return false; - } - player.setOnCompletionListener(this); - player.setOnErrorListener(this); - final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION); - intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId()); - intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName()); - intent.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC); - context.sendBroadcast(intent); - return true; - } - - /** - * Set the MediaPlayer to start when this MediaPlayer finishes playback. - * - * @param path The path of the file, or the http/rtsp URL of the stream you want to play - */ - @Override - public void setNextDataSource(@Nullable final String path) { - if (context == null) { - return; - } - try { - mCurrentMediaPlayer.setNextMediaPlayer(null); - } catch (IllegalArgumentException e) { - Log.i(TAG, "Next media player is current one, continuing"); - } catch (IllegalStateException e) { - Log.e(TAG, "Media player not initialized!"); - return; - } - if (mNextMediaPlayer != null) { - mNextMediaPlayer.release(); - mNextMediaPlayer = null; - } - if (path == null) { - return; - } - if (PreferenceUtil.INSTANCE.isGapLessPlayback()) { - mNextMediaPlayer = new MediaPlayer(); - mNextMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK); - mNextMediaPlayer.setAudioSessionId(getAudioSessionId()); - if (setDataSourceImpl(mNextMediaPlayer, path)) { + /** + * @param player The {@link MediaPlayer} to use + * @param path The path of the file, or the http/rtsp URL of the stream you want to play + * @return True if the player has been prepared and is ready to play, false otherwise + */ + private boolean setDataSourceImpl(@NonNull final MediaPlayer player, @NonNull final String path) { + if (context == null) { + return false; + } try { - mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer); - } catch (@NonNull IllegalArgumentException | IllegalStateException e) { - Log.e(TAG, "setNextDataSource: setNextMediaPlayer()", e); - if (mNextMediaPlayer != null) { + player.reset(); + player.setOnPreparedListener(null); + if (path.startsWith("content://")) { + player.setDataSource(context, Uri.parse(path)); + } else { + player.setDataSource(path); + } + player.setAudioStreamType(AudioManager.STREAM_MUSIC); + player.prepare(); + } catch (Exception e) { + return false; + } + player.setOnCompletionListener(this); + player.setOnErrorListener(this); + final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION); + intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId()); + intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName()); + intent.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC); + context.sendBroadcast(intent); + return true; + } + + /** + * Set the MediaPlayer to start when this MediaPlayer finishes playback. + * + * @param path The path of the file, or the http/rtsp URL of the stream you want to play + */ + @Override + public void setNextDataSource(@Nullable final String path) { + if (context == null) { + return; + } + try { + mCurrentMediaPlayer.setNextMediaPlayer(null); + } catch (IllegalArgumentException e) { + Log.i(TAG, "Next media player is current one, continuing"); + } catch (IllegalStateException e) { + Log.e(TAG, "Media player not initialized!"); + return; + } + if (mNextMediaPlayer != null) { mNextMediaPlayer.release(); mNextMediaPlayer = null; - } } - } else { + if (path == null) { + return; + } + if (PreferenceUtil.INSTANCE.isGapLessPlayback()) { + mNextMediaPlayer = new MediaPlayer(); + mNextMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK); + mNextMediaPlayer.setAudioSessionId(getAudioSessionId()); + if (setDataSourceImpl(mNextMediaPlayer, path)) { + try { + mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer); + } catch (@NonNull IllegalArgumentException | IllegalStateException e) { + Log.e(TAG, "setNextDataSource: setNextMediaPlayer()", e); + if (mNextMediaPlayer != null) { + mNextMediaPlayer.release(); + mNextMediaPlayer = null; + } + } + } else { + if (mNextMediaPlayer != null) { + mNextMediaPlayer.release(); + mNextMediaPlayer = null; + } + } + } + } + + /** + * Sets the callbacks + * + * @param callbacks The callbacks to use + */ + @Override + public void setCallbacks(@Nullable final Playback.PlaybackCallbacks callbacks) { + this.callbacks = callbacks; + } + + /** @return True if the player is ready to go, false otherwise */ + @Override + public boolean isInitialized() { + return mIsInitialized; + } + + /** Starts or resumes playback. */ + @Override + public boolean start() { + try { + mCurrentMediaPlayer.start(); + return true; + } catch (IllegalStateException e) { + return false; + } + } + + /** Resets the MediaPlayer to its uninitialized state. */ + @Override + public void stop() { + mCurrentMediaPlayer.reset(); + mIsInitialized = false; + } + + /** Releases resources associated with this MediaPlayer object. */ + @Override + public void release() { + stop(); + mCurrentMediaPlayer.release(); if (mNextMediaPlayer != null) { - mNextMediaPlayer.release(); - mNextMediaPlayer = null; + mNextMediaPlayer.release(); } - } } - } - /** - * Sets the callbacks - * - * @param callbacks The callbacks to use - */ - @Override - public void setCallbacks(@Nullable final Playback.PlaybackCallbacks callbacks) { - this.callbacks = callbacks; - } - - /** @return True if the player is ready to go, false otherwise */ - @Override - public boolean isInitialized() { - return mIsInitialized; - } - - /** Starts or resumes playback. */ - @Override - public boolean start() { - try { - mCurrentMediaPlayer.start(); - return true; - } catch (IllegalStateException e) { - return false; + /** Pauses playback. Call start() to resume. */ + @Override + public boolean pause() { + try { + mCurrentMediaPlayer.pause(); + return true; + } catch (IllegalStateException e) { + return false; + } } - } - /** Resets the MediaPlayer to its uninitialized state. */ - @Override - public void stop() { - mCurrentMediaPlayer.reset(); - mIsInitialized = false; - } - - /** Releases resources associated with this MediaPlayer object. */ - @Override - public void release() { - stop(); - mCurrentMediaPlayer.release(); - if (mNextMediaPlayer != null) { - mNextMediaPlayer.release(); + /** Checks whether the MultiPlayer is playing. */ + @Override + public boolean isPlaying() { + return mIsInitialized && mCurrentMediaPlayer.isPlaying(); } - } - /** Pauses playback. Call start() to resume. */ - @Override - public boolean pause() { - try { - mCurrentMediaPlayer.pause(); - return true; - } catch (IllegalStateException e) { - return false; + /** + * Gets the duration of the file. + * + * @return The duration in milliseconds + */ + @Override + public int duration() { + if (!mIsInitialized) { + return -1; + } + try { + return mCurrentMediaPlayer.getDuration(); + } catch (IllegalStateException e) { + return -1; + } } - } - /** Checks whether the MultiPlayer is playing. */ - @Override - public boolean isPlaying() { - return mIsInitialized && mCurrentMediaPlayer.isPlaying(); - } + /** + * Gets the current playback position. + * + * @return The current position in milliseconds + */ + @Override + public int position() { + if (!mIsInitialized) { + return -1; + } + try { + return mCurrentMediaPlayer.getCurrentPosition(); + } catch (IllegalStateException e) { + return -1; + } + } - /** - * Gets the duration of the file. - * - * @return The duration in milliseconds - */ - @Override - public int duration() { - if (!mIsInitialized) { - return -1; + /** + * Gets the current playback position. + * + * @param whereto The offset in milliseconds from the start to seek to + * @return The offset in milliseconds from the start to seek to + */ + @Override + public int seek(final int whereto) { + try { + mCurrentMediaPlayer.seekTo(whereto); + return whereto; + } catch (IllegalStateException e) { + return -1; + } } - try { - return mCurrentMediaPlayer.getDuration(); - } catch (IllegalStateException e) { - return -1; - } - } - /** - * Gets the current playback position. - * - * @return The current position in milliseconds - */ - @Override - public int position() { - if (!mIsInitialized) { - return -1; + @Override + public boolean setVolume(final float vol) { + try { + mCurrentMediaPlayer.setVolume(vol, vol); + return true; + } catch (IllegalStateException e) { + return false; + } } - try { - return mCurrentMediaPlayer.getCurrentPosition(); - } catch (IllegalStateException e) { - return -1; - } - } - /** - * Gets the current playback position. - * - * @param whereto The offset in milliseconds from the start to seek to - * @return The offset in milliseconds from the start to seek to - */ - @Override - public int seek(final int whereto) { - try { - mCurrentMediaPlayer.seekTo(whereto); - return whereto; - } catch (IllegalStateException e) { - return -1; + /** + * Sets the audio session ID. + * + * @param sessionId The audio session ID + */ + @Override + public boolean setAudioSessionId(final int sessionId) { + try { + mCurrentMediaPlayer.setAudioSessionId(sessionId); + return true; + } catch (@NonNull IllegalArgumentException | IllegalStateException e) { + return false; + } } - } - @Override - public boolean setVolume(final float vol) { - try { - mCurrentMediaPlayer.setVolume(vol, vol); - return true; - } catch (IllegalStateException e) { - return false; + /** + * Returns the audio session ID. + * + * @return The current audio session ID. + */ + @Override + public int getAudioSessionId() { + return mCurrentMediaPlayer.getAudioSessionId(); } - } - /** - * Sets the audio session ID. - * - * @param sessionId The audio session ID - */ - @Override - public boolean setAudioSessionId(final int sessionId) { - try { - mCurrentMediaPlayer.setAudioSessionId(sessionId); - return true; - } catch (@NonNull IllegalArgumentException | IllegalStateException e) { - return false; + /** {@inheritDoc} */ + @Override + public boolean onError(final MediaPlayer mp, final int what, final int extra) { + mIsInitialized = false; + mCurrentMediaPlayer.release(); + mCurrentMediaPlayer = new MediaPlayer(); + mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK); + if (context != null) { + Toast.makeText( + context, + context.getResources().getString(R.string.unplayable_file), + Toast.LENGTH_SHORT) + .show(); + } + return false; } - } - /** - * Returns the audio session ID. - * - * @return The current audio session ID. - */ - @Override - public int getAudioSessionId() { - return mCurrentMediaPlayer.getAudioSessionId(); - } - - /** {@inheritDoc} */ - @Override - public boolean onError(final MediaPlayer mp, final int what, final int extra) { - mIsInitialized = false; - mCurrentMediaPlayer.release(); - mCurrentMediaPlayer = new MediaPlayer(); - mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK); - if (context != null) { - Toast.makeText( - context, - context.getResources().getString(R.string.unplayable_file), - Toast.LENGTH_SHORT) - .show(); + /** {@inheritDoc} */ + @Override + public void onCompletion(final MediaPlayer mp) { + if (mp.equals(mCurrentMediaPlayer) && mNextMediaPlayer != null) { + mIsInitialized = false; + mCurrentMediaPlayer.release(); + mCurrentMediaPlayer = mNextMediaPlayer; + mIsInitialized = true; + mNextMediaPlayer = null; + if (callbacks != null) callbacks.onTrackWentToNext(); + } else { + if (callbacks != null) callbacks.onTrackEnded(); + } } - return false; - } - - /** {@inheritDoc} */ - @Override - public void onCompletion(final MediaPlayer mp) { - if (mp.equals(mCurrentMediaPlayer) && mNextMediaPlayer != null) { - mIsInitialized = false; - mCurrentMediaPlayer.release(); - mCurrentMediaPlayer = mNextMediaPlayer; - mIsInitialized = true; - mNextMediaPlayer = null; - if (callbacks != null) callbacks.onTrackWentToNext(); - } else { - if (callbacks != null) callbacks.onTrackEnded(); - } - } -} +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java b/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java index f0b8b110..6ea0e2f4 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java +++ b/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java @@ -14,8 +14,15 @@ package code.name.monkey.retromusic.service; +import static org.koin.java.KoinJavaComponent.get; +import static code.name.monkey.retromusic.ConstantsKt.ALBUM_ART_ON_LOCK_SCREEN; +import static code.name.monkey.retromusic.ConstantsKt.BLURRED_ALBUM_ART; +import static code.name.monkey.retromusic.ConstantsKt.CLASSIC_NOTIFICATION; +import static code.name.monkey.retromusic.ConstantsKt.COLORED_NOTIFICATION; +import static code.name.monkey.retromusic.ConstantsKt.CROSS_FADE_DURATION; +import static code.name.monkey.retromusic.ConstantsKt.TOGGLE_HEADSET; + import android.app.PendingIntent; -import android.app.Service; import android.appwidget.AppWidgetManager; import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; @@ -34,12 +41,14 @@ import android.os.Binder; import android.os.Build; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; +import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.PowerManager; import android.os.Process; import android.provider.MediaStore; +import android.support.v4.media.MediaBrowserCompat; import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.PlaybackStateCompat; @@ -50,11 +59,10 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.media.MediaBrowserServiceCompat; import androidx.preference.PreferenceManager; -import com.bumptech.glide.BitmapRequestBuilder; -import com.bumptech.glide.Glide; -import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.RequestBuilder; import com.bumptech.glide.request.target.SimpleTarget; import java.util.ArrayList; @@ -69,11 +77,13 @@ import code.name.monkey.retromusic.appwidgets.AppWidgetCard; import code.name.monkey.retromusic.appwidgets.AppWidgetClassic; import code.name.monkey.retromusic.appwidgets.AppWidgetSmall; import code.name.monkey.retromusic.appwidgets.AppWidgetText; +import code.name.monkey.retromusic.auto.AutoMediaIDHelper; +import code.name.monkey.retromusic.auto.AutoMusicProvider; import code.name.monkey.retromusic.glide.BlurTransformation; -import code.name.monkey.retromusic.glide.SongGlideRequest; +import code.name.monkey.retromusic.glide.GlideApp; +import code.name.monkey.retromusic.glide.RetroGlideExtension; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; import code.name.monkey.retromusic.helper.ShuffleHelper; -import code.name.monkey.retromusic.model.AbsCustomPlaylist; -import code.name.monkey.retromusic.model.Playlist; import code.name.monkey.retromusic.model.Song; import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist; import code.name.monkey.retromusic.providers.HistoryStore; @@ -84,20 +94,14 @@ import code.name.monkey.retromusic.service.notification.PlayingNotificationImpl; import code.name.monkey.retromusic.service.notification.PlayingNotificationOreo; import code.name.monkey.retromusic.service.playback.Playback; import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.PackageValidator; import code.name.monkey.retromusic.util.PreferenceUtil; import code.name.monkey.retromusic.util.RetroUtil; -import static code.name.monkey.retromusic.ConstantsKt.ALBUM_ART_ON_LOCK_SCREEN; -import static code.name.monkey.retromusic.ConstantsKt.BLURRED_ALBUM_ART; -import static code.name.monkey.retromusic.ConstantsKt.CLASSIC_NOTIFICATION; -import static code.name.monkey.retromusic.ConstantsKt.COLORED_NOTIFICATION; -import static code.name.monkey.retromusic.ConstantsKt.GAP_LESS_PLAYBACK; -import static code.name.monkey.retromusic.ConstantsKt.TOGGLE_HEADSET; - /** * @author Karim Abou Zeid (kabouzeid), Andrew Neal */ -public class MusicService extends Service +public class MusicService extends MediaBrowserServiceCompat implements SharedPreferences.OnSharedPreferenceChangeListener, Playback.PlaybackCallbacks { public static final String TAG = MusicService.class.getSimpleName(); @@ -168,6 +172,12 @@ public class MusicService extends Service @Nullable public Playback playback; + private PackageValidator mPackageValidator; + + private final AutoMusicProvider mMusicProvider = get(AutoMusicProvider.class); + + public boolean trackEndedByCrossfade = false; + public int position = -1; private AppWidgetBig appWidgetBig = AppWidgetBig.Companion.getInstance(); @@ -338,6 +348,7 @@ public class MusicService extends Service private ThrottledSeekHandler throttledSeekHandler; private Handler uiThreadHandler; private PowerManager.WakeLock wakeLock; + private AudioFader fader; private static Bitmap copy(Bitmap bitmap) { Bitmap.Config config = bitmap.getConfig(); @@ -375,7 +386,13 @@ public class MusicService extends Service musicPlayerHandlerThread.start(); playerHandler = new PlaybackHandler(this, musicPlayerHandlerThread.getLooper()); - playback = new MultiPlayer(this); + // Set MultiPlayer when crossfade duration is 0 i.e. off + if (PreferenceUtil.INSTANCE.getCrossFadeDuration() == 0) { + playback = new MultiPlayer(this); + } else { + playback = new CrossFadePlayer(this); + } + playback.setCallbacks(this); setupMediaSession(); @@ -437,6 +454,10 @@ public class MusicService extends Service registerHeadsetEvents(); registerBluetoothConnected(); + + mPackageValidator = new PackageValidator(this, R.xml.allowed_media_browser_callers); + mMusicProvider.setMusicService(this); + setSessionToken(mediaSession.getSessionToken()); } @Override @@ -536,6 +557,14 @@ public class MusicService extends Service return getSongAt(getPosition()); } + public Song getNextSong() { + if (!isLastTrack() || getRepeatMode() == REPEAT_MODE_THIS) { + return getSongAt(getNextPosition(false)); + } else { + return null; + } + } + @NonNull public MediaSessionCompat getMediaSession() { return mediaSession; @@ -764,19 +793,68 @@ public class MusicService extends Service @NonNull @Override public IBinder onBind(Intent intent) { + // For Android auto, need to call super, or onGetRoot won't be called. + if (intent != null && "android.media.browse.MediaBrowserService".equals(intent.getAction())) { + return super.onBind(intent); + } return musicBind; } + @Nullable + @Override + public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { + + // Check origin to ensure we're not allowing any arbitrary app to browse app contents + if (!mPackageValidator.isKnownCaller(clientPackageName, clientUid)) { + // Request from an untrusted package: return an empty browser root + return new BrowserRoot(AutoMediaIDHelper.MEDIA_ID_EMPTY_ROOT, null); + } + + return new BrowserRoot(AutoMediaIDHelper.MEDIA_ID_ROOT, null); + } + + @Override + public void onLoadChildren(@NonNull String parentId, @NonNull MediaBrowserServiceCompat.Result> result) { + result.sendResult(mMusicProvider.getChildren(parentId, getResources())); + } + @Override public void onSharedPreferenceChanged( @NonNull SharedPreferences sharedPreferences, @NonNull String key) { switch (key) { - case GAP_LESS_PLAYBACK: - if (sharedPreferences.getBoolean(key, false)) { - prepareNext(); - } else { + case CROSS_FADE_DURATION: + int progress = getSongProgressMillis(); + boolean wasPlaying = isPlaying(); + /* Switch to MultiPlayer if Crossfade duration is 0 and + Playback is not an instance of MultiPlayer */ + if (!(playback instanceof MultiPlayer) && PreferenceUtil.INSTANCE.getCrossFadeDuration() == 0) { if (playback != null) { - playback.setNextDataSource(null); + playback.release(); + } + playback = null; + playback = new MultiPlayer(this); + playback.setCallbacks(this); + if (openTrackAndPrepareNextAt(position)) { + seek(progress); + if (wasPlaying) { + play(); + } + } + } + /* Switch to CrossFadePlayer if Crossfade duration is greater than 0 and + Playback is not an instance of CrossFadePlayer */ + else if (!(playback instanceof CrossFadePlayer) && PreferenceUtil.INSTANCE.getCrossFadeDuration() > 0) { + if (playback != null) { + playback.release(); + } + playback = null; + playback = new CrossFadePlayer(this); + playback.setCallbacks(this); + if (openTrackAndPrepareNextAt(position)) { + seek(progress); + if (wasPlaying) { + play(); + } } } break; @@ -901,6 +979,22 @@ public class MusicService extends Service } public void pause() { + pausedByTransientLossOfFocus = false; + if (playback != null && playback.isPlaying()) { + if (fader != null) { + fader.stop(); + } + fader = new AudioFader(playback, PreferenceUtil.INSTANCE.getAudioFadeDuration(), false, () -> { + if (playback != null && playback.isPlaying()) { + playback.pause(); + notifyChange(PLAY_STATE_CHANGED); + } + }); + fader.start(); + } + } + + public void forcePause() { pausedByTransientLossOfFocus = false; if (playback != null && playback.isPlaying()) { playback.pause(); @@ -915,21 +1009,31 @@ public class MusicService extends Service if (!playback.isInitialized()) { playSongAt(getPosition()); } else { - playback.start(); - if (!becomingNoisyReceiverRegistered) { - registerReceiver(becomingNoisyReceiver, becomingNoisyReceiverIntentFilter); - becomingNoisyReceiverRegistered = true; + //Don't Start playing when it's casting + if (MusicPlayerRemote.INSTANCE.isCasting()) { + return; } - if (notHandledMetaChangedForCurrentTrack) { - handleChangeInternal(META_CHANGED); - notHandledMetaChangedForCurrentTrack = false; + if (fader != null) { + fader.stop(); } - notifyChange(PLAY_STATE_CHANGED); + fader = new AudioFader(playback, PreferenceUtil.INSTANCE.getAudioFadeDuration(), false, () -> { + if (!becomingNoisyReceiverRegistered) { + registerReceiver(becomingNoisyReceiver, becomingNoisyReceiverIntentFilter); + becomingNoisyReceiverRegistered = true; + } + if (notHandledMetaChangedForCurrentTrack) { + handleChangeInternal(META_CHANGED); + notHandledMetaChangedForCurrentTrack = false; + } - // fixes a bug where the volume would stay ducked because the - // AudioManager.AUDIOFOCUS_GAIN event is not sent - playerHandler.removeMessages(DUCK); - playerHandler.sendEmptyMessage(UNDUCK); + // fixes a bug where the volume would stay ducked because the + // AudioManager.AUDIOFOCUS_GAIN event is not sent + playerHandler.removeMessages(DUCK); + playerHandler.sendEmptyMessage(UNDUCK); + }); + playback.start(); + notifyChange(PLAY_STATE_CHANGED); + fader.start(); } } } else { @@ -955,6 +1059,14 @@ public class MusicService extends Service } public void playSongAtImpl(int position) { + if (!trackEndedByCrossfade) { + // This is only imp if we are using crossfade + if (playback instanceof CrossFadePlayer) { + ((CrossFadePlayer) playback).sourceChangedByUser(); + } + } else { + trackEndedByCrossfade = false; + } if (openTrackAndPrepareNextAt(position)) { play(); } else { @@ -978,7 +1090,7 @@ public class MusicService extends Service } } - public boolean prepareNextImpl() { + public void prepareNextImpl() { synchronized (this) { try { int nextPosition = getNextPosition(false); @@ -986,9 +1098,7 @@ public class MusicService extends Service playback.setNextDataSource(getTrackUri(Objects.requireNonNull(getSongAt(nextPosition)))); } this.nextPosition = nextPosition; - return true; } catch (Exception e) { - return false; } } } @@ -1176,9 +1286,7 @@ public class MusicService extends Service if (PreferenceUtil.INSTANCE.isAlbumArtOnLockScreen()) { final Point screenSize = RetroUtil.getScreenSize(MusicService.this); - final BitmapRequestBuilder request = SongGlideRequest.Builder.from(Glide.with(MusicService.this), song) - .checkIgnoreMediaStore(MusicService.this) - .asBitmap().build(); + final RequestBuilder request = GlideApp.with(MusicService.this).asBitmap().songCoverOptions(song).load(RetroGlideExtension.INSTANCE.getSongModel(song)); if (PreferenceUtil.INSTANCE.isBlurredAlbumArt()) { request.transform(new BlurTransformation.Builder(MusicService.this).build()); } @@ -1187,14 +1295,13 @@ public class MusicService extends Service public void run() { request.into(new SimpleTarget(screenSize.x, screenSize.y) { @Override - public void onLoadFailed(Exception e, Drawable errorDrawable) { - super.onLoadFailed(e, errorDrawable); + public void onLoadFailed(Drawable errorDrawable) { + super.onLoadFailed(errorDrawable); mediaSession.setMetadata(metaData.build()); } @Override - public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) { - + public void onResourceReady(@NonNull Bitmap resource, @Nullable com.bumptech.glide.request.transition.Transition transition) { metaData.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, copy(resource)); mediaSession.setMetadata(metaData.build()); } @@ -1238,6 +1345,7 @@ public class MusicService extends Service case META_CHANGED: updateNotification(); updateMediaSessionMetaData(); + updateMediaSessionPlaybackState(); savePosition(); savePositionInTrack(); final Song currentSong = getCurrentSong(); @@ -1266,6 +1374,7 @@ public class MusicService extends Service return playback.setDataSource(getTrackUri(Objects.requireNonNull(getCurrentSong()))); } } catch (Exception e) { + e.printStackTrace(); return false; } } diff --git a/app/src/main/java/code/name/monkey/retromusic/service/PlaybackHandler.java b/app/src/main/java/code/name/monkey/retromusic/service/PlaybackHandler.java index 5928a533..82e90cfb 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/PlaybackHandler.java +++ b/app/src/main/java/code/name/monkey/retromusic/service/PlaybackHandler.java @@ -25,10 +25,13 @@ import android.media.AudioManager; import android.os.Handler; import android.os.Looper; import android.os.Message; + import androidx.annotation.NonNull; -import code.name.monkey.retromusic.util.PreferenceUtil; + import java.lang.ref.WeakReference; +import code.name.monkey.retromusic.util.PreferenceUtil; + class PlaybackHandler extends Handler { @NonNull private final WeakReference mService; @@ -93,6 +96,7 @@ class PlaybackHandler extends Handler { break; case TRACK_ENDED: + service.trackEndedByCrossfade = true; // if there is a timer finished, don't continue if (service.pendingQuit || service.getRepeatMode() == REPEAT_MODE_NONE && service.isLastTrack()) { @@ -143,7 +147,7 @@ class PlaybackHandler extends Handler { case AudioManager.AUDIOFOCUS_LOSS: // Lost focus for an unbounded amount of time: stop playback and release media playback - service.pause(); + service.forcePause(); break; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: @@ -151,7 +155,7 @@ class PlaybackHandler extends Handler { // playback. We don't release the media playback because playback // is likely to resume boolean wasPlaying = service.isPlaying(); - service.pause(); + service.forcePause(); service.setPausedByTransientLossOfFocus(wasPlaying); break; diff --git a/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationImpl.kt b/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationImpl.kt index dd7af83d..bdc0b07d 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationImpl.kt +++ b/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationImpl.kt @@ -29,7 +29,8 @@ import code.name.monkey.retromusic.R import code.name.monkey.retromusic.activities.MainActivity import code.name.monkey.retromusic.db.PlaylistEntity import code.name.monkey.retromusic.db.toSongEntity -import code.name.monkey.retromusic.glide.SongGlideRequest +import code.name.monkey.retromusic.glide.GlideApp +import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService.* @@ -37,9 +38,9 @@ import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.RetroColorUtil import com.bumptech.glide.Glide -import com.bumptech.glide.request.animation.GlideAnimation import com.bumptech.glide.request.target.SimpleTarget import com.bumptech.glide.request.target.Target +import com.bumptech.glide.request.transition.Transition import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import org.koin.core.KoinComponent @@ -79,11 +80,11 @@ class PlayingNotificationImpl : PlayingNotification(), KoinComponent { .getDimensionPixelSize(R.dimen.notification_big_image_size) service.runOnUiThread { if (target != null) { - Glide.clear(target) + Glide.with(service).clear(target) } - target = SongGlideRequest.Builder.from(Glide.with(service), song) - .checkIgnoreMediaStore(service) - .generatePalette(service).build() + target = GlideApp.with(service).asBitmapPalette().songCoverOptions(song) + .load(RetroGlideExtension.getSongModel(song)) + //.checkIgnoreMediaStore() .centerCrop() .into(object : SimpleTarget( bigNotificationImageSize, @@ -91,7 +92,7 @@ class PlayingNotificationImpl : PlayingNotification(), KoinComponent { ) { override fun onResourceReady( resource: BitmapPaletteWrapper, - glideAnimation: GlideAnimation + transition: Transition? ) { update( resource.bitmap, @@ -99,8 +100,8 @@ class PlayingNotificationImpl : PlayingNotification(), KoinComponent { ) } - override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) { - super.onLoadFailed(e, errorDrawable) + override fun onLoadFailed(errorDrawable: Drawable?) { + super.onLoadFailed(errorDrawable) update(null, Color.TRANSPARENT) } diff --git a/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationOreo.kt b/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationOreo.kt index 7612c7a0..7a5ce7c8 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationOreo.kt +++ b/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationOreo.kt @@ -28,7 +28,8 @@ import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.retromusic.R import code.name.monkey.retromusic.activities.MainActivity -import code.name.monkey.retromusic.glide.SongGlideRequest +import code.name.monkey.retromusic.glide.GlideApp +import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.service.MusicService @@ -38,9 +39,9 @@ import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil.createBitmap import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import com.bumptech.glide.Glide -import com.bumptech.glide.request.animation.GlideAnimation import com.bumptech.glide.request.target.SimpleTarget import com.bumptech.glide.request.target.Target +import com.bumptech.glide.request.transition.Transition /** * @author Hemanth S (h4h13). @@ -95,11 +96,10 @@ class PlayingNotificationOreo : PlayingNotification() { .getDimensionPixelSize(R.dimen.notification_big_image_size) service.runOnUiThread { if (target != null) { - Glide.clear(target) + Glide.with(service).clear(target) } - target = SongGlideRequest.Builder.from(Glide.with(service), song) - .checkIgnoreMediaStore(service) - .generatePalette(service).build() + target = GlideApp.with(service).asBitmapPalette().songCoverOptions(song) + .load(RetroGlideExtension.getSongModel(song)) .centerCrop() .into(object : SimpleTarget( bigNotificationImageSize, @@ -107,7 +107,7 @@ class PlayingNotificationOreo : PlayingNotification() { ) { override fun onResourceReady( resource: BitmapPaletteWrapper, - glideAnimation: GlideAnimation + transition: Transition? ) { /* val mediaNotificationProcessor = MediaNotificationProcessor( service, @@ -119,8 +119,8 @@ class PlayingNotificationOreo : PlayingNotification() { update(resource.bitmap, colors.backgroundColor) } - override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) { - super.onLoadFailed(e, errorDrawable) + override fun onLoadFailed(errorDrawable: Drawable?) { + super.onLoadFailed(errorDrawable) update( null, resolveColor(service, R.attr.colorSurface, Color.WHITE) diff --git a/app/src/main/java/code/name/monkey/retromusic/transform/CascadingPageTransformer.kt b/app/src/main/java/code/name/monkey/retromusic/transform/CascadingPageTransformer.kt index 6330f062..9db5d5b7 100644 --- a/app/src/main/java/code/name/monkey/retromusic/transform/CascadingPageTransformer.kt +++ b/app/src/main/java/code/name/monkey/retromusic/transform/CascadingPageTransformer.kt @@ -14,7 +14,6 @@ package code.name.monkey.retromusic.transform -import android.annotation.SuppressLint import android.view.View import androidx.viewpager.widget.ViewPager @@ -22,11 +21,6 @@ class CascadingPageTransformer : ViewPager.PageTransformer { private var mScaleOffset = 40 - fun setScaleOffset(mScaleOffset: Int) { - this.mScaleOffset = mScaleOffset - } - - @SuppressLint("NewApi") override fun transformPage(page: View, position: Float) { if (position <= 0.0f) {//被滑动的那页 position 是-下标~ 0 page.translationX = 0f @@ -34,14 +28,6 @@ class CascadingPageTransformer : ViewPager.PageTransformer { page.rotation = 45 * position //X轴偏移 li: 300/3 * -0.1 = -10 page.translationX = page.width / 3 * position - } else if (position <= 1f) { - val scale = (page.width - mScaleOffset * position) / page.width.toFloat() - - page.scaleX = scale - page.scaleY = scale - - page.translationX = -page.width * position - page.translationY = mScaleOffset * 0.8f * position } else { //缩放比例 val scale = (page.width - mScaleOffset * position) / page.width.toFloat() @@ -50,7 +36,7 @@ class CascadingPageTransformer : ViewPager.PageTransformer { page.scaleY = scale page.translationX = -page.width * position - page.translationY = mScaleOffset * 0.7f * position + page.translationY = mScaleOffset * 0.8f * position } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/transform/HingeTransformation.kt b/app/src/main/java/code/name/monkey/retromusic/transform/HingeTransformation.kt index c3804623..9dc2fedc 100644 --- a/app/src/main/java/code/name/monkey/retromusic/transform/HingeTransformation.kt +++ b/app/src/main/java/code/name/monkey/retromusic/transform/HingeTransformation.kt @@ -16,6 +16,7 @@ package code.name.monkey.retromusic.transform import android.view.View import androidx.viewpager.widget.ViewPager +import kotlin.math.abs class HingeTransformation : ViewPager.PageTransformer { override fun transformPage(page: View, position: Float) { @@ -25,22 +26,30 @@ class HingeTransformation : ViewPager.PageTransformer { page.pivotY = 0f - if (position < -1) { // [-Infinity,-1) - // This page is way off-screen to the left. - page.alpha = 0f - - } else if (position <= 0) { // [-1,0] - page.rotation = 90 * Math.abs(position) - page.alpha = 1 - Math.abs(position) - - } else if (position <= 1) { // (0,1] - page.rotation = 0f - page.alpha = 1f - - } else { // (1,+Infinity] - // This page is way off-screen to the right. - page.alpha = 0f - + when { + position < -1 -> { // [-Infinity,-1) + // This page is way off-screen to the left. + page.alpha = 0f + // The Page is off-screen but it may still interfere with + // click events of current page if + // it's visibility is not set to Gone + page.visibility = View.GONE + } + position <= 0 -> { // [-1,0] + page.rotation = 90 * abs(position) + page.alpha = 1 - abs(position) + page.visibility = View.VISIBLE + } + position <= 1 -> { // (0,1] + page.rotation = 0f + page.alpha = 1f + page.visibility = View.VISIBLE + } + else -> { // (1,+Infinity] + // This page is way off-screen to the right. + page.alpha = 0f + page.visibility = View.GONE + } } } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/util/AppRater.kt b/app/src/main/java/code/name/monkey/retromusic/util/AppRater.kt index ded848d2..83237739 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/AppRater.kt +++ b/app/src/main/java/code/name/monkey/retromusic/util/AppRater.kt @@ -60,7 +60,7 @@ object AppRater { } } - editor.commit() + editor.apply() } private fun showPlayStoreReviewDialog(context: Activity, editor: SharedPreferences.Editor) { diff --git a/app/src/main/java/code/name/monkey/retromusic/util/ArtistSignatureUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/ArtistSignatureUtil.java index 6418dae5..5a069f5a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/ArtistSignatureUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/ArtistSignatureUtil.java @@ -14,11 +14,12 @@ package code.name.monkey.retromusic.util; -import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; + import androidx.annotation.NonNull; -import com.bumptech.glide.signature.StringSignature; + +import com.bumptech.glide.signature.ObjectKey; /** @author Karim Abou Zeid (kabouzeid) */ public class ArtistSignatureUtil { @@ -39,16 +40,15 @@ public class ArtistSignatureUtil { return sInstance; } - @SuppressLint("CommitPrefEdits") public void updateArtistSignature(String artistName) { - mPreferences.edit().putLong(artistName, System.currentTimeMillis()).commit(); + mPreferences.edit().putLong(artistName, System.currentTimeMillis()).apply(); } public long getArtistSignatureRaw(String artistName) { return mPreferences.getLong(artistName, 0); } - public StringSignature getArtistSignature(String artistName) { - return new StringSignature(String.valueOf(getArtistSignatureRaw(artistName))); + public ObjectKey getArtistSignature(String artistName) { + return new ObjectKey(String.valueOf(getArtistSignatureRaw(artistName))); } } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/AutoGeneratedPlaylistBitmap.java b/app/src/main/java/code/name/monkey/retromusic/util/AutoGeneratedPlaylistBitmap.java index f219ee39..cf2e38f3 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/AutoGeneratedPlaylistBitmap.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/AutoGeneratedPlaylistBitmap.java @@ -31,49 +31,19 @@ public class AutoGeneratedPlaylistBitmap { */ public static Bitmap getBitmap( Context context, List songPlaylist, boolean round, boolean blur) { - if (songPlaylist == null || songPlaylist.isEmpty()) return null; - long start = System.currentTimeMillis(); - // lấy toàn bộ album id, loại bỏ trùng nhau + if (songPlaylist == null || songPlaylist.isEmpty()) return getDefaultBitmap(context, round); + if (songPlaylist.size() == 1) return getBitmapWithAlbumId(context, songPlaylist.get(0).getAlbumId()); List albumID = new ArrayList<>(); for (Song song : songPlaylist) { if (!albumID.contains(song.getAlbumId())) albumID.add(song.getAlbumId()); } - - long start2 = System.currentTimeMillis() - start; - - // lấy toàn bộ art tồn tại - List art = new ArrayList(); + List art = new ArrayList<>(); for (Long id : albumID) { Bitmap bitmap = getBitmapWithAlbumId(context, id); if (bitmap != null) art.add(bitmap); - if (art.size() == 6) break; + if (art.size() == 9) break; } return MergedImageUtils.INSTANCE.joinImages(art); - /* - - long start3 = System.currentTimeMillis() - start2 - start; - Bitmap ret; - switch (art.size()) { - // lấy hình mặc định - case 0: - ret = getDefaultBitmap(context, round).copy(Bitmap.Config.ARGB_8888, false); - break; - // dùng hình duy nhất - case 1: - if (round) - ret = BitmapEditor.getRoundedCornerBitmap(art.get(0), art.get(0).getWidth() / 40); - else ret = art.get(0); - break; - // từ 2 trở lên ta cần vẽ canvas - default: - ret = getBitmapCollection(art, round); - } - int w = ret.getWidth(); - if (blur) - return BitmapEditor.GetRoundedBitmapWithBlurShadow(context, ret, w / 24, w / 24, w / 24, w / 24, 0, 200, w / 40, 1); - - Log.d(TAG, "getBitmap: time = " + (System.currentTimeMillis() - start) + ", start2 = " + start2 + ", start3 = " + start3); - return ret;*/ } private static Bitmap getBitmapCollection(ArrayList art, boolean round) { @@ -175,9 +145,9 @@ public class AutoGeneratedPlaylistBitmap { private static Bitmap getBitmapWithAlbumId(@NonNull Context context, Long id) { try { return Glide.with(context) - .load(MusicUtil.INSTANCE.getMediaStoreAlbumCoverUri(id)) .asBitmap() - .into(200, 200) + .load(MusicUtil.INSTANCE.getMediaStoreAlbumCoverUri(id)) + .submit(200, 200) .get(); } catch (Exception e) { return null; @@ -185,8 +155,6 @@ public class AutoGeneratedPlaylistBitmap { } public static Bitmap getDefaultBitmap(@NonNull Context context, boolean round) { - if (round) - return BitmapFactory.decodeResource(context.getResources(), R.drawable.default_album_art); return BitmapFactory.decodeResource(context.getResources(), R.drawable.default_album_art); } } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/ColorUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/ColorUtil.java index 0f94d96a..e8f91f5d 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/ColorUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/ColorUtil.java @@ -1,9 +1,11 @@ package code.name.monkey.retromusic.util; import android.graphics.Bitmap; + import androidx.annotation.ColorInt; import androidx.annotation.Nullable; import androidx.palette.graphics.Palette; + import java.util.Collections; import java.util.Comparator; diff --git a/app/src/main/java/code/name/monkey/retromusic/util/Compressor.java b/app/src/main/java/code/name/monkey/retromusic/util/Compressor.java index 21050b74..6259a52b 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/Compressor.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/Compressor.java @@ -16,6 +16,7 @@ package code.name.monkey.retromusic.util; import android.content.Context; import android.graphics.Bitmap; + import java.io.File; import java.io.IOException; diff --git a/app/src/main/java/code/name/monkey/retromusic/util/CustomArtistImageUtil.kt b/app/src/main/java/code/name/monkey/retromusic/util/CustomArtistImageUtil.kt index a3b221b8..3dd3ea63 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/CustomArtistImageUtil.kt +++ b/app/src/main/java/code/name/monkey/retromusic/util/CustomArtistImageUtil.kt @@ -27,8 +27,8 @@ import code.name.monkey.retromusic.App import code.name.monkey.retromusic.model.Artist import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.request.animation.GlideAnimation import com.bumptech.glide.request.target.SimpleTarget +import com.bumptech.glide.request.transition.Transition import java.io.BufferedOutputStream import java.io.File import java.io.FileOutputStream @@ -38,34 +38,26 @@ import java.util.* class CustomArtistImageUtil private constructor(context: Context) { - private val mPreferences: SharedPreferences - - init { - mPreferences = context.applicationContext.getSharedPreferences( - CUSTOM_ARTIST_IMAGE_PREFS, - Context.MODE_PRIVATE - ) - } + private val mPreferences: SharedPreferences = context.applicationContext.getSharedPreferences( + CUSTOM_ARTIST_IMAGE_PREFS, + Context.MODE_PRIVATE + ) fun setCustomArtistImage(artist: Artist, uri: Uri) { Glide.with(App.getContext()) - .load(uri) .asBitmap() + .load(uri) .diskCacheStrategy(DiskCacheStrategy.NONE) .skipMemoryCache(true) .into(object : SimpleTarget() { - override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) { - super.onLoadFailed(e, errorDrawable) - e!!.printStackTrace() - Toast.makeText(App.getContext(), e.toString(), Toast.LENGTH_LONG).show() + override fun onLoadFailed(errorDrawable: Drawable?) { + super.onLoadFailed(errorDrawable) + Toast.makeText(App.getContext(), "Load Failed", Toast.LENGTH_LONG).show() } - override fun onResourceReady( - resource: Bitmap, - glideAnimation: GlideAnimation - ) { + @SuppressLint("StaticFieldLeak") + override fun onResourceReady(resource: Bitmap, transition: Transition?) { object : AsyncTask() { - @SuppressLint("ApplySharedPref") override fun doInBackground(vararg params: Void): Void? { val dir = File(App.getContext().filesDir, FOLDER_NAME) if (!dir.exists()) { @@ -87,7 +79,7 @@ class CustomArtistImageUtil private constructor(context: Context) { } if (succesful) { - mPreferences.edit().putBoolean(getFileName(artist), true).commit() + mPreferences.edit().putBoolean(getFileName(artist), true).apply() ArtistSignatureUtil.getInstance(App.getContext()) .updateArtistSignature(artist.name) App.getContext().contentResolver.notifyChange( @@ -102,6 +94,7 @@ class CustomArtistImageUtil private constructor(context: Context) { }) } + @SuppressLint("StaticFieldLeak") fun resetCustomArtistImage(artist: Artist) { object : AsyncTask() { @SuppressLint("ApplySharedPref") diff --git a/app/src/main/java/code/name/monkey/retromusic/util/DensityUtil.kt b/app/src/main/java/code/name/monkey/retromusic/util/DensityUtil.kt index 2e36de9a..51a7cc42 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/DensityUtil.kt +++ b/app/src/main/java/code/name/monkey/retromusic/util/DensityUtil.kt @@ -13,9 +13,7 @@ */ package code.name.monkey.retromusic.util -import android.app.Activity import android.content.Context -import android.util.DisplayMetrics import android.util.TypedValue /** @@ -23,14 +21,12 @@ import android.util.TypedValue */ object DensityUtil { fun getScreenHeight(context: Context): Int { - val displayMetrics = DisplayMetrics() - (context as Activity).windowManager.defaultDisplay.getMetrics(displayMetrics) + val displayMetrics = context.resources.displayMetrics return displayMetrics.heightPixels } fun getScreenWidth(context: Context): Int { - val displayMetrics = DisplayMetrics() - (context as Activity).windowManager.defaultDisplay.getMetrics(displayMetrics) + val displayMetrics = context.resources.displayMetrics return displayMetrics.widthPixels } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/FileUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/FileUtil.java index 9b3126e7..0023ec5f 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/FileUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/FileUtil.java @@ -19,11 +19,10 @@ import android.database.Cursor; import android.os.Environment; import android.provider.MediaStore; import android.webkit.MimeTypeMap; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import code.name.monkey.retromusic.model.Song; -import code.name.monkey.retromusic.repository.RealSongRepository; -import code.name.monkey.retromusic.repository.SortedCursor; + import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; @@ -37,6 +36,10 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.repository.RealSongRepository; +import code.name.monkey.retromusic.repository.SortedCursor; + public final class FileUtil { private FileUtil() {} diff --git a/app/src/main/java/code/name/monkey/retromusic/util/ImageUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/ImageUtil.java index a4169fc1..bcba5274 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/ImageUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/ImageUtil.java @@ -26,17 +26,20 @@ import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.media.ExifInterface; import android.os.Build; + import androidx.annotation.ColorInt; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; -import code.name.monkey.appthemehelper.util.TintHelper; + import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import code.name.monkey.appthemehelper.util.TintHelper; + /** * Created on : June 18, 2016 Author : zetbaitsu Name : Zetra GitHub : https://github.com/zetbaitsu */ diff --git a/app/src/main/java/code/name/monkey/retromusic/util/LyricUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/LyricUtil.java index 8b506064..49f85f13 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/LyricUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/LyricUtil.java @@ -15,120 +15,175 @@ package code.name.monkey.retromusic.util; import android.util.Base64; +import android.util.Log; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; + import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; +import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; -/** Created by hefuyi on 2016/11/8. */ +import code.name.monkey.retromusic.model.Song; + +/** + * Created by hefuyi on 2016/11/8. + */ public class LyricUtil { - private static final String lrcRootPath = - android.os.Environment.getExternalStorageDirectory().toString() + "/RetroMusic/lyrics/"; - private static final String TAG = "LyricUtil"; + private static final String lrcRootPath = + android.os.Environment.getExternalStorageDirectory().toString() + "/RetroMusic/lyrics/"; + private static final String TAG = "LyricUtil"; - @Nullable - public static File writeLrcToLoc( - @NonNull String title, @NonNull String artist, @NonNull String lrcContext) { - FileWriter writer = null; - try { - File file = new File(getLrcPath(title, artist)); - if (!file.getParentFile().exists()) { - file.getParentFile().mkdirs(); - } - writer = new FileWriter(getLrcPath(title, artist)); - writer.write(lrcContext); - return file; - } catch (IOException e) { - e.printStackTrace(); - return null; - } finally { - try { - if (writer != null) writer.close(); - } catch (IOException e) { - e.printStackTrace(); - } + @Nullable + public static File writeLrcToLoc( + @NonNull String title, @NonNull String artist, @NonNull String lrcContext) { + FileWriter writer = null; + try { + File file = new File(getLrcPath(title, artist)); + if (!file.getParentFile().exists()) { + file.getParentFile().mkdirs(); + } + writer = new FileWriter(getLrcPath(title, artist)); + writer.write(lrcContext); + return file; + } catch (IOException e) { + e.printStackTrace(); + return null; + } finally { + try { + if (writer != null) writer.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } } - } - public static boolean deleteLrcFile(@NonNull String title, @NonNull String artist) { - File file = new File(getLrcPath(title, artist)); - return file.delete(); - } + //So in Retro, Lrc file can be same folder as Music File or in RetroMusic Folder + // In this case we pass location of the file and Contents to write to file + public static void writeLrc(@NonNull Song song, @NonNull String lrcContext) { + FileWriter writer = null; + File location; + try { + if (isLrcOriginalFileExist(song.getData())) { + location = getLocalLyricOriginalFile(song.getData()); + } else if (isLrcFileExist(song.getTitle(), song.getArtistName())) { + location = getLocalLyricFile(song.getTitle(), song.getArtistName()); + } else { + location = new File(getLrcPath(song.getTitle(), song.getArtistName())); + if (!location.getParentFile().exists()) { + location.getParentFile().mkdirs(); + } + } + writer = new FileWriter(location); + writer.write(lrcContext); - public static boolean isLrcFileExist(@NonNull String title, @NonNull String artist) { - File file = new File(getLrcPath(title, artist)); - return file.exists(); - } - - public static boolean isLrcOriginalFileExist(@NonNull String path) { - File file = new File(getLrcOriginalPath(path)); - return file.exists(); - } - - @Nullable - public static File getLocalLyricFile(@NonNull String title, @NonNull String artist) { - File file = new File(getLrcPath(title, artist)); - if (file.exists()) { - return file; - } else { - return null; + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (writer != null) writer.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } } - } - @Nullable - public static File getLocalLyricOriginalFile(@NonNull String path) { - File file = new File(getLrcOriginalPath(path)); - if (file.exists()) { - return file; - } else { - return null; + public static boolean deleteLrcFile(@NonNull String title, @NonNull String artist) { + File file = new File(getLrcPath(title, artist)); + return file.delete(); } - } - private static String getLrcPath(String title, String artist) { - return lrcRootPath + title + " - " + artist + ".lrc"; - } - - private static String getLrcOriginalPath(String filePath) { - return filePath.replace(filePath.substring(filePath.lastIndexOf(".") + 1), "lrc"); - } - - @NonNull - public static String decryptBASE64(@NonNull String str) { - if (str == null || str.length() == 0) { - return null; + public static boolean isLrcFileExist(@NonNull String title, @NonNull String artist) { + File file = new File(getLrcPath(title, artist)); + return file.exists(); } - byte[] encode = str.getBytes(StandardCharsets.UTF_8); - // base64 解密 - return new String( - Base64.decode(encode, 0, encode.length, Base64.DEFAULT), StandardCharsets.UTF_8); - } - @NonNull - public static String getStringFromFile(@NonNull String title, @NonNull String artist) - throws Exception { - File file = new File(getLrcPath(title, artist)); - FileInputStream fin = new FileInputStream(file); - String ret = convertStreamToString(fin); - fin.close(); - return ret; - } - - private static String convertStreamToString(InputStream is) throws Exception { - BufferedReader reader = new BufferedReader(new InputStreamReader(is)); - StringBuilder sb = new StringBuilder(); - String line = null; - while ((line = reader.readLine()) != null) { - sb.append(line).append("\n"); + public static boolean isLrcOriginalFileExist(@NonNull String path) { + File file = new File(getLrcOriginalPath(path)); + return file.exists(); } - reader.close(); - return sb.toString(); - } + + @Nullable + public static File getLocalLyricFile(@NonNull String title, @NonNull String artist) { + File file = new File(getLrcPath(title, artist)); + if (file.exists()) { + return file; + } else { + return null; + } + } + + @Nullable + public static File getLocalLyricOriginalFile(@NonNull String path) { + File file = new File(getLrcOriginalPath(path)); + if (file.exists()) { + return file; + } else { + return null; + } + } + + private static String getLrcPath(String title, String artist) { + return lrcRootPath + title + " - " + artist + ".lrc"; + } + + private static String getLrcOriginalPath(String filePath) { + return filePath.replace(filePath.substring(filePath.lastIndexOf(".") + 1), "lrc"); + } + + @NonNull + public static String decryptBASE64(@NonNull String str) { + if (str == null || str.length() == 0) { + return null; + } + byte[] encode = str.getBytes(StandardCharsets.UTF_8); + // base64 解密 + return new String( + Base64.decode(encode, 0, encode.length, Base64.DEFAULT), StandardCharsets.UTF_8); + } + + @NonNull + public static String getStringFromFile(@NonNull String title, @NonNull String artist) + throws Exception { + File file = new File(getLrcPath(title, artist)); + FileInputStream fin = new FileInputStream(file); + String ret = convertStreamToString(fin); + fin.close(); + return ret; + } + + private static String convertStreamToString(InputStream is) throws Exception { + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line).append("\n"); + } + reader.close(); + return sb.toString(); + } + + public static String getStringFromLrc(File file) { + try { + BufferedReader reader = new BufferedReader(new FileReader(file)); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line).append("\n"); + } + reader.close(); + return sb.toString(); + } catch (Exception e) { + Log.i("Error", "Error Occurred"); + } + return ""; + } + } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/MergedImageUtils.kt b/app/src/main/java/code/name/monkey/retromusic/util/MergedImageUtils.kt index 8fcfcdf8..e4864bc7 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/MergedImageUtils.kt +++ b/app/src/main/java/code/name/monkey/retromusic/util/MergedImageUtils.kt @@ -13,7 +13,7 @@ internal object MergedImageUtils { fun joinImages(list: List): Bitmap { assertBackgroundThread() - val arranged = arrangeBitmaps(list.shuffled()) + val arranged = arrangeBitmaps(list) val mergedImage = create( arranged, diff --git a/app/src/main/java/code/name/monkey/retromusic/util/MusicUtil.kt b/app/src/main/java/code/name/monkey/retromusic/util/MusicUtil.kt index 4d4746af..ed89e34d 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/MusicUtil.kt +++ b/app/src/main/java/code/name/monkey/retromusic/util/MusicUtil.kt @@ -29,8 +29,10 @@ import code.name.monkey.retromusic.repository.RealSongRepository import code.name.monkey.retromusic.repository.Repository import code.name.monkey.retromusic.repository.SongRepository import code.name.monkey.retromusic.service.MusicService +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.jaudiotagger.audio.AudioFileIO import org.jaudiotagger.tag.FieldKey import org.koin.core.KoinComponent @@ -127,7 +129,7 @@ object MusicUtil : KoinComponent { } catch (e: Exception) { e.printStackTrace() } - if (lyrics == null || lyrics.trim { it <= ' ' }.isEmpty() || !AbsSynchronizedLyrics + if (lyrics == null || lyrics.trim { it <= ' ' }.isEmpty() || AbsSynchronizedLyrics .isSynchronized(lyrics) ) { val dir = file.absoluteFile.parentFile @@ -205,7 +207,7 @@ object MusicUtil : KoinComponent { return getSongCountString(context, songs.size) } - fun getReadableDurationString(songDurationMillis: Long): String? { + fun getReadableDurationString(songDurationMillis: Long): String { var minutes = songDurationMillis / 1000 / 60 val seconds = songDurationMillis / 1000 % 60 return if (minutes < 60) { @@ -217,7 +219,7 @@ object MusicUtil : KoinComponent { ) } else { val hours = minutes / 60 - minutes = minutes % 60 + minutes %= 60 String.format( Locale.getDefault(), "%02d:%02d:%02d", @@ -228,13 +230,13 @@ object MusicUtil : KoinComponent { } } - fun getSectionName(musicMediaTitle: String?): String { - var musicMediaTitle = musicMediaTitle + fun getSectionName(mediaTitle: String?): String { + var musicMediaTitle = mediaTitle return try { if (TextUtils.isEmpty(musicMediaTitle)) { return "" } - musicMediaTitle = musicMediaTitle!!.trim { it <= ' ' }.toLowerCase() + musicMediaTitle = musicMediaTitle!!.trim { it <= ' ' }.lowercase() if (musicMediaTitle.startsWith("the ")) { musicMediaTitle = musicMediaTitle.substring(4) } else if (musicMediaTitle.startsWith("a ")) { @@ -242,7 +244,7 @@ object MusicUtil : KoinComponent { } if (musicMediaTitle.isEmpty()) { "" - } else musicMediaTitle.substring(0, 1).toUpperCase() + } else musicMediaTitle.substring(0, 1).uppercase() } catch (e: Exception) { "" } @@ -257,10 +259,21 @@ object MusicUtil : KoinComponent { fun getSongFileUri(songId: Long): Uri { return ContentUris.withAppendedId( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, - songId.toLong() + songId ) } + fun getSongFilePath(context: Context, uri: Uri): String? { + val projection = arrayOf(MediaStore.MediaColumns.DATA) + return context.contentResolver.query(uri, projection, null, null, null)?.use { + if (it.moveToFirst()) { + it.getString(0) + } else { + "" + } + } + } + fun getTotalDuration(songs: List): Long { var duration: Long = 0 for (i in songs.indices) { @@ -299,7 +312,7 @@ object MusicUtil : KoinComponent { if (artistName == Artist.UNKNOWN_ARTIST_DISPLAY_NAME) { return true } - val tempName = artistName!!.trim { it <= ' ' }.toLowerCase() + val tempName = artistName!!.trim { it <= ' ' }.lowercase() return tempName == "unknown" || tempName == "" } @@ -442,7 +455,7 @@ object MusicUtil : KoinComponent { } } - fun deleteTracks(context: Context, songs: List) { + suspend fun deleteTracks(context: Context, songs: List) { val projection = arrayOf(BaseColumns._ID, MediaStore.MediaColumns.DATA) val selection = StringBuilder() selection.append(BaseColumns._ID + " IN (") @@ -501,13 +514,19 @@ object MusicUtil : KoinComponent { } cursor.close() } - Toast.makeText( - context, - context.getString(R.string.deleted_x_songs, deletedCount), - Toast.LENGTH_SHORT - ).show() + withContext(Dispatchers.Main) { + Toast.makeText( + context, + context.getString(R.string.deleted_x_songs, deletedCount), + Toast.LENGTH_SHORT + ).show() + } + } catch (ignored: SecurityException) { } } + fun songByGenre(genreId: Long): Song { + return repository.getSongByGenre(genreId) + } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/util/NavigationUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/NavigationUtil.java index d1aa3c41..f0d324c7 100755 --- a/app/src/main/java/code/name/monkey/retromusic/util/NavigationUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/NavigationUtil.java @@ -21,8 +21,12 @@ import android.content.Context; import android.content.Intent; import android.media.audiofx.AudioEffect; import android.widget.Toast; + import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; + +import org.jetbrains.annotations.NotNull; + import code.name.monkey.retromusic.R; import code.name.monkey.retromusic.activities.DriveModeActivity; import code.name.monkey.retromusic.activities.LicenseActivity; @@ -34,7 +38,6 @@ import code.name.monkey.retromusic.activities.UserInfoActivity; import code.name.monkey.retromusic.activities.WhatsNewActivity; import code.name.monkey.retromusic.activities.bugreport.BugReportActivity; import code.name.monkey.retromusic.helper.MusicPlayerRemote; -import org.jetbrains.annotations.NotNull; public class NavigationUtil { diff --git a/app/src/main/java/code/name/monkey/retromusic/util/PackageValidator.kt b/app/src/main/java/code/name/monkey/retromusic/util/PackageValidator.kt new file mode 100644 index 00000000..d75bd28b --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/PackageValidator.kt @@ -0,0 +1,347 @@ +/* + * Copyright (c) 2019 Hemanth Savarala. + * + * 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.util + + +import android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE +import android.Manifest.permission.MEDIA_CONTENT_CONTROL +import android.annotation.SuppressLint +import android.content.Context +import android.content.pm.PackageInfo +import android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED +import android.content.pm.PackageManager +import android.content.res.XmlResourceParser +import android.os.Process +import android.support.v4.media.session.MediaSessionCompat +import android.util.Base64 +import android.util.Log +import androidx.annotation.XmlRes +import androidx.media.MediaBrowserServiceCompat +import code.name.monkey.retromusic.BuildConfig +import org.xmlpull.v1.XmlPullParserException +import java.io.IOException +import java.security.MessageDigest +import java.security.NoSuchAlgorithmException + +/** + * Validates that the calling package is authorized to browse a [MediaBrowserServiceCompat]. + * + * The list of allowed signing certificates and their corresponding package names is defined in + * res/xml/allowed_media_browser_callers.xml. + * + * If you want to add a new caller to allowed_media_browser_callers.xml and you don't know + * its signature, this class will print to logcat (INFO level) a message with the proper + * xml tags to add to allow the caller. + * + * For more information, see res/xml/allowed_media_browser_callers.xml. + */ +class PackageValidator( + context: Context, + @XmlRes xmlResId: Int +) { + private val context: Context + private val packageManager: PackageManager + + private val certificateWhitelist: Map + private val platformSignature: String + + private val callerChecked = mutableMapOf>() + + init { + val parser = context.resources.getXml(xmlResId) + this.context = context.applicationContext + this.packageManager = this.context.packageManager + + certificateWhitelist = buildCertificateWhitelist(parser) + platformSignature = getSystemSignature() + } + + /** + * Checks whether the caller attempting to connect to a [MediaBrowserServiceCompat] is known. + * See [MusicService.onGetRoot] for where this is utilized. + * + * @param callingPackage The package name of the caller. + * @param callingUid The user id of the caller. + * @return `true` if the caller is known, `false` otherwise. + */ + fun isKnownCaller(callingPackage: String, callingUid: Int): Boolean { + // If the caller has already been checked, return the previous result here. + val (checkedUid, checkResult) = callerChecked[callingPackage] ?: Pair(0, false) + if (checkedUid == callingUid) { + return checkResult + } + + /** + * Because some of these checks can be slow, we save the results in [callerChecked] after + * this code is run. + * + * In particular, there's little reason to recompute the calling package's certificate + * signature (SHA-256) each call. + * + * This is safe to do as we know the UID matches the package's UID (from the check above), + * and app UIDs are set at install time. Additionally, a package name + UID is guaranteed to + * be constant until a reboot. (After a reboot then a previously assigned UID could be + * reassigned.) + */ + + // Build the caller info for the rest of the checks here. + val callerPackageInfo = buildCallerInfo(callingPackage) + ?: throw IllegalStateException("Caller wasn't found in the system?") + + // Verify that things aren't ... broken. (This test should always pass.) + if (callerPackageInfo.uid != callingUid) { + throw IllegalStateException("Caller's package UID doesn't match caller's actual UID?") + } + + val callerSignature = callerPackageInfo.signature + val isPackageInWhitelist = certificateWhitelist[callingPackage]?.signatures?.first { + it.signature == callerSignature + } != null + + val isCallerKnown = when { + // If it's our own app making the call, allow it. + callingUid == Process.myUid() -> true + // If it's one of the apps on the whitelist, allow it. + isPackageInWhitelist -> true + // If the system is making the call, allow it. + callingUid == Process.SYSTEM_UID -> true + // If the app was signed by the same certificate as the platform itself, also allow it. + callerSignature == platformSignature -> true + /** + * [MEDIA_CONTENT_CONTROL] permission is only available to system applications, and + * while it isn't required to allow these apps to connect to a + * [MediaBrowserServiceCompat], allowing this ensures optimal compatability with apps + * such as Android TV and the Google Assistant. + */ + callerPackageInfo.permissions.contains(MEDIA_CONTENT_CONTROL) -> true + /** + * This last permission can be specifically granted to apps, and, in addition to + * allowing them to retrieve notifications, it also allows them to connect to an + * active [MediaSessionCompat]. + * As with the above, it's not required to allow apps holding this permission to + * connect to your [MediaBrowserServiceCompat], but it does allow easy comparability + * with apps such as Wear OS. + */ + callerPackageInfo.permissions.contains(BIND_NOTIFICATION_LISTENER_SERVICE) -> true + // If none of the pervious checks succeeded, then the caller is unrecognized. + else -> false + } + + if (!isCallerKnown) { + logUnknownCaller(callerPackageInfo) + } + + // Save our work for next time. + callerChecked[callingPackage] = Pair(callingUid, isCallerKnown) + return isCallerKnown + } + + /** + * Logs an info level message with details of how to add a caller to the allowed callers list + * when the app is debuggable. + */ + private fun logUnknownCaller(callerPackageInfo: CallerPackageInfo) { + if (BuildConfig.DEBUG && callerPackageInfo.signature != null) { + Log.i(TAG, "PackageValidator call" + callerPackageInfo.name + callerPackageInfo.packageName + callerPackageInfo.signature) + } + } + + /** + * Builds a [CallerPackageInfo] for a given package that can be used for all the + * various checks that are performed before allowing an app to connect to a + * [MediaBrowserServiceCompat]. + */ + private fun buildCallerInfo(callingPackage: String): CallerPackageInfo? { + val packageInfo = getPackageInfo(callingPackage) ?: return null + + val appName = packageInfo.applicationInfo.loadLabel(packageManager).toString() + val uid = packageInfo.applicationInfo.uid + val signature = getSignature(packageInfo) + + val requestedPermissions = packageInfo.requestedPermissions + val permissionFlags = packageInfo.requestedPermissionsFlags + val activePermissions = mutableSetOf() + requestedPermissions?.forEachIndexed { index, permission -> + if (permissionFlags[index] and REQUESTED_PERMISSION_GRANTED != 0) { + activePermissions += permission + } + } + + return CallerPackageInfo(appName, callingPackage, uid, signature, activePermissions.toSet()) + } + + /** + * Looks up the [PackageInfo] for a package name. + * This requests both the signatures (for checking if an app is on the whitelist) and + * the app's permissions, which allow for more flexibility in the whitelist. + * + * @return [PackageInfo] for the package name or null if it's not found. + */ + @SuppressLint("PackageManagerGetSignatures") + private fun getPackageInfo(callingPackage: String): PackageInfo? = + packageManager.getPackageInfo(callingPackage, + PackageManager.GET_SIGNATURES or PackageManager.GET_PERMISSIONS) + + /** + * Gets the signature of a given package's [PackageInfo]. + * + * The "signature" is a SHA-256 hash of the public key of the signing certificate used by + * the app. + * + * If the app is not found, or if the app does not have exactly one signature, this method + * returns `null` as the signature. + */ + private fun getSignature(packageInfo: PackageInfo): String? { + // Security best practices dictate that an app should be signed with exactly one (1) + // signature. Because of this, if there are multiple signatures, reject it. + if (packageInfo.signatures == null || packageInfo.signatures.size != 1) { + return null + } else { + val certificate = packageInfo.signatures[0].toByteArray() + return getSignatureSha256(certificate) + } + } + + private fun buildCertificateWhitelist(parser: XmlResourceParser): Map { + + val certificateWhitelist = LinkedHashMap() + try { + var eventType = parser.next() + while (eventType != XmlResourceParser.END_DOCUMENT) { + if (eventType == XmlResourceParser.START_TAG) { + val callerInfo = when (parser.name) { + "signing_certificate" -> parseV1Tag(parser) + "signature" -> parseV2Tag(parser) + else -> null + } + + callerInfo?.let { info -> + val packageName = info.packageName + val existingCallerInfo = certificateWhitelist[packageName] + if (existingCallerInfo != null) { + existingCallerInfo.signatures += callerInfo.signatures + } else { + certificateWhitelist[packageName] = callerInfo + } + } + } + + eventType = parser.next() + } + } catch (xmlException: XmlPullParserException) { + Log.e(TAG, "Could not read allowed callers from XML.", xmlException) + } catch (ioException: IOException) { + Log.e(TAG, "Could not read allowed callers from XML.", ioException) + } + + return certificateWhitelist + } + + /** + * Parses a v1 format tag. See allowed_media_browser_callers.xml for more details. + */ + private fun parseV1Tag(parser: XmlResourceParser): KnownCallerInfo { + val name = parser.getAttributeValue(null, "name") + val packageName = parser.getAttributeValue(null, "package") + val isRelease = parser.getAttributeBooleanValue(null, "release", false) + val certificate = parser.nextText().replace(WHITESPACE_REGEX, "") + val signature = getSignatureSha256(certificate) + + val callerSignature = KnownSignature(signature, isRelease) + return KnownCallerInfo(name, packageName, mutableSetOf(callerSignature)) + } + + /** + * Parses a v2 format tag. See allowed_media_browser_callers.xml for more details. + */ + private fun parseV2Tag(parser: XmlResourceParser): KnownCallerInfo { + val name = parser.getAttributeValue(null, "name") + val packageName = parser.getAttributeValue(null, "package") + + val callerSignatures = mutableSetOf() + var eventType = parser.next() + while (eventType != XmlResourceParser.END_TAG) { + val isRelease = parser.getAttributeBooleanValue(null, "release", false) + val signature = parser.nextText().replace(WHITESPACE_REGEX, "").toLowerCase() + callerSignatures += KnownSignature(signature, isRelease) + + eventType = parser.next() + } + + return KnownCallerInfo(name, packageName, callerSignatures) + } + + /** + * Finds the Android platform signing key signature. This key is never null. + */ + private fun getSystemSignature(): String = + getPackageInfo(ANDROID_PLATFORM)?.let { platformInfo -> + getSignature(platformInfo) + } ?: throw IllegalStateException("Platform signature not found") + + /** + * Creates a SHA-256 signature given a Base64 encoded certificate. + */ + private fun getSignatureSha256(certificate: String): String { + return getSignatureSha256(Base64.decode(certificate, Base64.DEFAULT)) + } + + /** + * Creates a SHA-256 signature given a certificate byte array. + */ + private fun getSignatureSha256(certificate: ByteArray): String { + val md: MessageDigest + try { + md = MessageDigest.getInstance("SHA256") + } catch (noSuchAlgorithmException: NoSuchAlgorithmException) { + Log.e(TAG, "No such algorithm: $noSuchAlgorithmException") + throw RuntimeException("Could not find SHA256 hash algorithm", noSuchAlgorithmException) + } + md.update(certificate) + + // This code takes the byte array generated by `md.digest()` and joins each of the bytes + // to a string, applying the string format `%02x` on each digit before it's appended, with + // a colon (':') between each of the items. + // For example: input=[0,2,4,6,8,10,12], output="00:02:04:06:08:0a:0c" + return md.digest().joinToString(":") { String.format("%02x", it) } + } + + private data class KnownCallerInfo( + internal val name: String, + internal val packageName: String, + internal val signatures: MutableSet + ) + + private data class KnownSignature( + internal val signature: String, + internal val release: Boolean + ) + + /** + * Convenience class to hold all of the information about an app that's being checked + * to see if it's a known caller. + */ + private data class CallerPackageInfo( + internal val name: String, + internal val packageName: String, + internal val uid: Int, + internal val signature: String?, + internal val permissions: Set + ) +} + +private const val TAG = "PackageValidator" +private const val ANDROID_PLATFORM = "android" +private val WHITESPACE_REGEX = "\\s|\\n".toRegex() diff --git a/app/src/main/java/code/name/monkey/retromusic/util/PlaylistsUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/PlaylistsUtil.java index bd3a2c9d..9b677849 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/PlaylistsUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/PlaylistsUtil.java @@ -25,18 +25,21 @@ import android.os.Environment; import android.provider.BaseColumns; import android.provider.MediaStore; import android.widget.Toast; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + import code.name.monkey.retromusic.R; import code.name.monkey.retromusic.db.PlaylistWithSongs; import code.name.monkey.retromusic.helper.M3UWriter; import code.name.monkey.retromusic.model.Playlist; import code.name.monkey.retromusic.model.PlaylistSong; import code.name.monkey.retromusic.model.Song; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; public class PlaylistsUtil { diff --git a/app/src/main/java/code/name/monkey/retromusic/util/PreferenceUtil.kt b/app/src/main/java/code/name/monkey/retromusic/util/PreferenceUtil.kt index 8cca0eed..62a24375 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/PreferenceUtil.kt +++ b/app/src/main/java/code/name/monkey/retromusic/util/PreferenceUtil.kt @@ -17,7 +17,7 @@ import code.name.monkey.retromusic.helper.SortOrder.* import code.name.monkey.retromusic.model.CategoryInfo import code.name.monkey.retromusic.transform.* import code.name.monkey.retromusic.util.theme.ThemeMode -import com.google.android.material.bottomnavigation.LabelVisibilityMode +import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.gson.Gson import com.google.gson.JsonSyntaxException import com.google.gson.reflect.TypeToken @@ -218,11 +218,10 @@ object PreferenceUtil { TOGGLE_ADD_CONTROLS, false ) - val typeHomeBanner - get() = sharedPreferences.getStringOrDefault( - TYPE_HOME_BANNER, "0" - ).toInt() - + val isHomeBanner + get() = sharedPreferences.getBoolean( + TOGGLE_HOME_BANNER, false + ) var isClassicNotification get() = sharedPreferences.getBoolean(CLASSIC_NOTIFICATION, false) set(value) = sharedPreferences.edit { putBoolean(CLASSIC_NOTIFICATION, value) } @@ -416,10 +415,10 @@ object PreferenceUtil { ).toInt() val typedArray = App.getContext() .resources.obtainTypedArray(R.array.pref_home_grid_style_layout) - val layoutRes = typedArray.getResourceId(position, 4) + val layoutRes = typedArray.getResourceId(position, 0) typedArray.recycle() return if (layoutRes == 0) { - R.layout.item_artist + R.layout.item_image } else layoutRes } @@ -428,11 +427,11 @@ object PreferenceUtil { return when (sharedPreferences.getStringOrDefault( TAB_TEXT_MODE, "1" ).toInt()) { - 1 -> LabelVisibilityMode.LABEL_VISIBILITY_LABELED - 0 -> LabelVisibilityMode.LABEL_VISIBILITY_AUTO - 2 -> LabelVisibilityMode.LABEL_VISIBILITY_SELECTED - 3 -> LabelVisibilityMode.LABEL_VISIBILITY_UNLABELED - else -> LabelVisibilityMode.LABEL_VISIBILITY_LABELED + 1 -> BottomNavigationView.LABEL_VISIBILITY_LABELED + 0 -> BottomNavigationView.LABEL_VISIBILITY_AUTO + 2 -> BottomNavigationView.LABEL_VISIBILITY_SELECTED + 3 -> BottomNavigationView.LABEL_VISIBILITY_UNLABELED + else -> BottomNavigationView.LABEL_VISIBILITY_LABELED } } @@ -556,8 +555,7 @@ object PreferenceUtil { fun getRecentlyPlayedCutoffTimeMillis(): Long { val calendarUtil = CalendarUtil() - val interval: Long - interval = when (sharedPreferences.getString(RECENTLY_PLAYED_CUTOFF, "")) { + val interval: Long = when (sharedPreferences.getString(RECENTLY_PLAYED_CUTOFF, "")) { "today" -> calendarUtil.elapsedToday "this_week" -> calendarUtil.elapsedWeek "past_seven_days" -> calendarUtil.getElapsedDays(7) @@ -583,4 +581,35 @@ object PreferenceUtil { } return (System.currentTimeMillis() - interval) / 1000 } + + val homeSuggestions: Boolean + get() = sharedPreferences.getBoolean( + TOGGLE_SUGGESTIONS, + true + ) + + var audioFadeDuration + get() = sharedPreferences + .getInt(AUDIO_FADE_DURATION, 0) + set(value) = sharedPreferences.edit { putInt(AUDIO_FADE_DURATION, value) } + + var showLyrics: Boolean + get() = sharedPreferences.getBoolean(SHOW_LYRICS, false) + set(value) = sharedPreferences.edit { putBoolean(SHOW_LYRICS, value) } + + val rememberLastTab: Boolean + get() = sharedPreferences.getBoolean(REMEMBER_LAST_TAB, true) + + var lastTab: Int + get() = sharedPreferences + .getInt(LAST_USED_TAB, 0) + set(value) = sharedPreferences.edit { putInt(LAST_USED_TAB, value) } + + val isWhiteList: Boolean + get() = sharedPreferences.getBoolean(WHITELIST_MUSIC, false) + + var crossFadeDuration + get() = sharedPreferences + .getInt(CROSS_FADE_DURATION, 0) + set(value) = sharedPreferences.edit { putInt(CROSS_FADE_DURATION, value) } } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/RetroColorUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/RetroColorUtil.java index 47fbdd8b..061eb17f 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/RetroColorUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/RetroColorUtil.java @@ -18,16 +18,19 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Color; + import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.palette.graphics.Palette; -import code.name.monkey.appthemehelper.util.ColorUtil; + import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; +import code.name.monkey.appthemehelper.util.ColorUtil; + public class RetroColorUtil { public static int desaturateColor(int color, float ratio) { float[] hsv = new float[3]; diff --git a/app/src/main/java/code/name/monkey/retromusic/util/RetroUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/RetroUtil.java index 61630ce2..f06aade5 100755 --- a/app/src/main/java/code/name/monkey/retromusic/util/RetroUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/RetroUtil.java @@ -35,14 +35,21 @@ import android.view.View; import android.view.Window; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; + import androidx.annotation.ColorInt; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.text.DecimalFormat; +import java.util.Collections; +import java.util.List; + import code.name.monkey.appthemehelper.util.TintHelper; import code.name.monkey.retromusic.App; -import java.text.DecimalFormat; public class RetroUtil { @@ -203,8 +210,43 @@ public class RetroUtil { public static void setAllowDrawUnderStatusBar(@NonNull Window window) { window - .getDecorView() - .setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + .getDecorView() + .setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + } + + public static String getIpAddress(boolean useIPv4) { + try { + List interfaces = + Collections.list(NetworkInterface.getNetworkInterfaces()); + for (NetworkInterface intf : interfaces) { + List addrs = Collections.list(intf.getInetAddresses()); + for (InetAddress addr : addrs) { + if (!addr.isLoopbackAddress()) { + String sAddr = addr.getHostAddress(); + //boolean isIPv4 = InetAddressUtils.isIPv4Address(sAddr); + boolean isIPv4 = sAddr.indexOf(':') < 0; + if (useIPv4) { + if (isIPv4) return sAddr; + } else { + if (!isIPv4) { + int delim = sAddr.indexOf('%'); // drop ip6 zone suffix + if (delim < 0) { + return sAddr.toUpperCase(); + } else { + return sAddr.substring( + 0, + delim + ).toUpperCase(); + } + } + } + } + } + + } + } catch (Exception ignored) { + } + return ""; } } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/RingtoneManager.kt b/app/src/main/java/code/name/monkey/retromusic/util/RingtoneManager.kt index cf8226d9..c7040ccd 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/RingtoneManager.kt +++ b/app/src/main/java/code/name/monkey/retromusic/util/RingtoneManager.kt @@ -23,7 +23,6 @@ import android.provider.BaseColumns import android.provider.MediaStore import android.provider.Settings import android.widget.Toast -import androidx.appcompat.app.AlertDialog import code.name.monkey.retromusic.R import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.MusicUtil.getSongFileUri diff --git a/app/src/main/java/code/name/monkey/retromusic/util/RippleUtils.java b/app/src/main/java/code/name/monkey/retromusic/util/RippleUtils.java index 53916675..335e46ba 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/RippleUtils.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/RippleUtils.java @@ -5,6 +5,7 @@ import android.content.res.ColorStateList; import android.graphics.Color; import android.os.Build; import android.util.StateSet; + import androidx.annotation.ColorInt; import androidx.annotation.Nullable; import androidx.core.graphics.ColorUtils; diff --git a/app/src/main/java/code/name/monkey/retromusic/util/SAFUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/SAFUtil.java index 64150ed0..c846bc64 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/SAFUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/SAFUtil.java @@ -26,20 +26,24 @@ import android.provider.DocumentsContract; import android.text.TextUtils; import android.util.Log; import android.widget.Toast; + import androidx.annotation.Nullable; import androidx.documentfile.provider.DocumentFile; import androidx.fragment.app.Fragment; -import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.model.Song; + +import org.jaudiotagger.audio.AudioFile; +import org.jaudiotagger.audio.exceptions.CannotWriteException; +import org.jaudiotagger.audio.generic.Utils; + import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import org.jaudiotagger.audio.AudioFile; -import org.jaudiotagger.audio.exceptions.CannotWriteException; -import org.jaudiotagger.audio.generic.Utils; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.model.Song; public class SAFUtil { diff --git a/app/src/main/java/code/name/monkey/retromusic/util/SwipeAndDragHelper.java b/app/src/main/java/code/name/monkey/retromusic/util/SwipeAndDragHelper.java index 2ab25e18..2fdf3827 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/SwipeAndDragHelper.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/SwipeAndDragHelper.java @@ -15,6 +15,7 @@ package code.name.monkey.retromusic.util; import android.graphics.Canvas; + import androidx.annotation.NonNull; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.RecyclerView; diff --git a/app/src/main/java/code/name/monkey/retromusic/util/ViewUtil.kt b/app/src/main/java/code/name/monkey/retromusic/util/ViewUtil.kt index 936f3520..c4e1a176 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/ViewUtil.kt +++ b/app/src/main/java/code/name/monkey/retromusic/util/ViewUtil.kt @@ -23,7 +23,6 @@ import android.widget.ProgressBar import android.widget.SeekBar import androidx.core.graphics.BlendModeColorFilterCompat import androidx.core.graphics.BlendModeCompat.SRC_IN -import androidx.core.view.ViewCompat import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.MaterialValueHelper @@ -83,8 +82,8 @@ object ViewUtil { } fun hitTest(v: View, x: Int, y: Int): Boolean { - val tx = (ViewCompat.getTranslationX(v) + 0.5f).toInt() - val ty = (ViewCompat.getTranslationY(v) + 0.5f).toInt() + val tx = (v.translationX + 0.5f).toInt() + val ty = (v.translationY + 0.5f).toInt() val left = v.left + tx val right = v.right + tx val top = v.top + ty diff --git a/app/src/main/java/code/name/monkey/retromusic/util/color/MediaNotificationProcessor.java b/app/src/main/java/code/name/monkey/retromusic/util/color/MediaNotificationProcessor.java index 2e1cb4b1..6f17f982 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/color/MediaNotificationProcessor.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/color/MediaNotificationProcessor.java @@ -25,14 +25,17 @@ import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Handler; + import androidx.annotation.ColorInt; import androidx.annotation.FloatRange; import androidx.annotation.NonNull; import androidx.palette.graphics.Palette; + +import java.util.List; + import code.name.monkey.appthemehelper.util.ATHUtil; import code.name.monkey.appthemehelper.util.ColorUtil; import code.name.monkey.retromusic.R; -import java.util.List; /** A class the processes media notifications and extracts the right text and background colors. */ public class MediaNotificationProcessor { @@ -75,12 +78,7 @@ public class MediaNotificationProcessor { private static final String TAG = "ColorPicking"; private float[] mFilteredBackgroundHsl = null; private Palette.Filter mBlackWhiteFilter = - new Palette.Filter() { - @Override - public boolean isAllowed(int rgb, @NonNull float[] hsl) { - return !isWhiteOrBlack(hsl); - } - }; + (rgb, hsl) -> !isWhiteOrBlack(hsl); private boolean mIsLowPriority; private int backgroundColor; private int secondaryTextColor; @@ -140,18 +138,10 @@ public class MediaNotificationProcessor { this.drawable = drawable; final Handler handler = new Handler(); new Thread( - new Runnable() { - @Override - public void run() { - getMediaPalette(); - handler.post( - new Runnable() { - @Override - public void run() { - onPaletteLoadedListener.onPaletteLoaded(MediaNotificationProcessor.this); - } - }); - } + () -> { + getMediaPalette(); + handler.post( + () -> onPaletteLoadedListener.onPaletteLoaded(MediaNotificationProcessor.this)); }) .start(); } @@ -175,44 +165,40 @@ public class MediaNotificationProcessor { double factor = Math.sqrt((float) RESIZE_BITMAP_AREA / area); width = (int) (factor * width); height = (int) (factor * height); + } + bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, width, height); + drawable.draw(canvas); - bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - drawable.setBounds(0, 0, width, height); - drawable.draw(canvas); - - // for the background we only take the left side of the image to ensure - // a smooth transition - Palette.Builder paletteBuilder = - Palette.from(bitmap) - .setRegion(0, 0, bitmap.getWidth() / 2, bitmap.getHeight()) - .clearFilters() // we want all colors, red / white / black ones too! - .resizeBitmapArea(RESIZE_BITMAP_AREA); - Palette palette = paletteBuilder.generate(); - backgroundColor = findBackgroundColorAndFilter(drawable); - // we want most of the full region again, slightly shifted to the right - float textColorStartWidthFraction = 0.4f; - paletteBuilder.setRegion( - (int) (bitmap.getWidth() * textColorStartWidthFraction), - 0, - bitmap.getWidth(), - bitmap.getHeight()); - if (mFilteredBackgroundHsl != null) { - paletteBuilder.addFilter( - new Palette.Filter() { - @Override - public boolean isAllowed(int rgb, @NonNull float[] hsl) { + // for the background we only take the left side of the image to ensure + // a smooth transition + Palette.Builder paletteBuilder = + Palette.from(bitmap) + .setRegion(0, 0, bitmap.getWidth() / 2, bitmap.getHeight()) + .clearFilters() // we want all colors, red / white / black ones too! + .resizeBitmapArea(RESIZE_BITMAP_AREA); + Palette palette; + backgroundColor = findBackgroundColorAndFilter(drawable); + // we want most of the full region again, slightly shifted to the right + float textColorStartWidthFraction = 0.4f; + paletteBuilder.setRegion( + (int) (bitmap.getWidth() * textColorStartWidthFraction), + 0, + bitmap.getWidth(), + bitmap.getHeight()); + if (mFilteredBackgroundHsl != null) { + paletteBuilder.addFilter( + (rgb, hsl) -> { // at least 10 degrees hue difference float diff = Math.abs(hsl[0] - mFilteredBackgroundHsl[0]); return diff > 10 && diff < 350; - } - }); - } - paletteBuilder.addFilter(mBlackWhiteFilter); - palette = paletteBuilder.generate(); - int foregroundColor = selectForegroundColor(backgroundColor, palette); - ensureColors(backgroundColor, foregroundColor); + }); } + paletteBuilder.addFilter(mBlackWhiteFilter); + palette = paletteBuilder.generate(); + int foregroundColor = selectForegroundColor(backgroundColor, palette); + ensureColors(backgroundColor, foregroundColor); } } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/color/NotificationColorUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/color/NotificationColorUtil.java index fe8e4346..1b7fb758 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/color/NotificationColorUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/color/NotificationColorUtil.java @@ -28,14 +28,17 @@ import android.text.style.ForegroundColorSpan; import android.text.style.TextAppearanceSpan; import android.util.Log; import android.util.Pair; + import androidx.annotation.ColorInt; import androidx.annotation.FloatRange; import androidx.annotation.IntRange; import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; -import code.name.monkey.retromusic.R; + import java.util.WeakHashMap; +import code.name.monkey.retromusic.R; + /** * Helper class to process legacy (Holo) notifications to make them look like material * notifications. diff --git a/app/src/main/java/code/name/monkey/retromusic/views/BaselineGridTextView.java b/app/src/main/java/code/name/monkey/retromusic/views/BaselineGridTextView.java index 711094db..87d4487f 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/BaselineGridTextView.java +++ b/app/src/main/java/code/name/monkey/retromusic/views/BaselineGridTextView.java @@ -19,10 +19,13 @@ import android.content.res.TypedArray; import android.graphics.Paint; import android.util.AttributeSet; import android.util.TypedValue; + import androidx.annotation.FontRes; -import code.name.monkey.retromusic.R; + import com.google.android.material.textview.MaterialTextView; +import code.name.monkey.retromusic.R; + public class BaselineGridTextView extends MaterialTextView { private final float FOUR_DIP; diff --git a/app/src/main/java/code/name/monkey/retromusic/views/BreadCrumbLayout.java b/app/src/main/java/code/name/monkey/retromusic/views/BreadCrumbLayout.java index c13802b1..b35840f6 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/BreadCrumbLayout.java +++ b/app/src/main/java/code/name/monkey/retromusic/views/BreadCrumbLayout.java @@ -26,16 +26,19 @@ import android.widget.HorizontalScrollView; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; + import androidx.annotation.ColorInt; import androidx.annotation.NonNull; -import code.name.monkey.appthemehelper.util.ATHUtil; -import code.name.monkey.retromusic.R; + import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.retromusic.R; + /** @author Aidan Follestad (afollestad), modified for Phonograph by Karim Abou Zeid (kabouzeid) */ public class BreadCrumbLayout extends HorizontalScrollView implements View.OnClickListener { diff --git a/app/src/main/java/code/name/monkey/retromusic/views/CircularImageView.java b/app/src/main/java/code/name/monkey/retromusic/views/CircularImageView.java index 2c0dccad..9bdf582f 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/CircularImageView.java +++ b/app/src/main/java/code/name/monkey/retromusic/views/CircularImageView.java @@ -27,7 +27,9 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.Log; + import androidx.appcompat.widget.AppCompatImageView; + import code.name.monkey.retromusic.R; public class CircularImageView extends AppCompatImageView { diff --git a/app/src/main/java/code/name/monkey/retromusic/views/ContributorsView.java b/app/src/main/java/code/name/monkey/retromusic/views/ContributorsView.java index 1ce50b0a..4e4c1a74 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/ContributorsView.java +++ b/app/src/main/java/code/name/monkey/retromusic/views/ContributorsView.java @@ -24,8 +24,10 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.FrameLayout; import android.widget.TextView; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; + import code.name.monkey.retromusic.R; public class ContributorsView extends FrameLayout { diff --git a/app/src/main/java/code/name/monkey/retromusic/views/ListItemView.kt b/app/src/main/java/code/name/monkey/retromusic/views/ListItemView.kt index b0f1b753..a034eaf6 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/ListItemView.kt +++ b/app/src/main/java/code/name/monkey/retromusic/views/ListItemView.kt @@ -16,18 +16,19 @@ package code.name.monkey.retromusic.views import android.content.Context import android.util.AttributeSet -import android.view.View +import android.view.LayoutInflater import android.widget.FrameLayout import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.ListItemViewNoCardBinding import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.show -import kotlinx.android.synthetic.main.list_item_view.view.* /** * Created by hemanths on 2019-10-02. */ class ListItemView : FrameLayout { + private lateinit var binding: ListItemViewNoCardBinding constructor(context: Context) : super(context) { init(context, null) @@ -46,27 +47,27 @@ class ListItemView : FrameLayout { } private fun init(context: Context, attrs: AttributeSet?) { - View.inflate(context, R.layout.list_item_view_no_card, this) + binding = ListItemViewNoCardBinding.inflate(LayoutInflater.from(context)) + addView(binding.root) val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ListItemView) if (typedArray.hasValue(R.styleable.ListItemView_listItemIcon)) { - icon.setImageDrawable(typedArray.getDrawable(R.styleable.ListItemView_listItemIcon)) + binding.icon.setImageDrawable(typedArray.getDrawable(R.styleable.ListItemView_listItemIcon)) } else { - - icon.hide() + binding.icon.hide() } - title.text = typedArray.getText(R.styleable.ListItemView_listItemTitle) + binding.title.text = typedArray.getText(R.styleable.ListItemView_listItemTitle) if (typedArray.hasValue(R.styleable.ListItemView_listItemSummary)) { - summary.text = typedArray.getText(R.styleable.ListItemView_listItemSummary) + binding.summary.text = typedArray.getText(R.styleable.ListItemView_listItemSummary) } else { - summary.hide() + binding.summary.hide() } typedArray.recycle() } fun setSummary(appVersion: String) { - summary.show() - summary.text = appVersion + binding.summary.show() + binding.summary.text = appVersion } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/views/NetworkImageView.java b/app/src/main/java/code/name/monkey/retromusic/views/NetworkImageView.java index f115ff94..8f0916ec 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/NetworkImageView.java +++ b/app/src/main/java/code/name/monkey/retromusic/views/NetworkImageView.java @@ -17,11 +17,14 @@ package code.name.monkey.retromusic.views; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import code.name.monkey.retromusic.R; + import com.bumptech.glide.Glide; +import code.name.monkey.retromusic.R; + /** @author Hemanth S (h4h13). */ public class NetworkImageView extends CircularImageView { diff --git a/app/src/main/java/code/name/monkey/retromusic/views/PermissionItem.kt b/app/src/main/java/code/name/monkey/retromusic/views/PermissionItem.kt index 7e627f24..dfe1d015 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/PermissionItem.kt +++ b/app/src/main/java/code/name/monkey/retromusic/views/PermissionItem.kt @@ -3,12 +3,13 @@ package code.name.monkey.retromusic.views import android.content.Context import android.content.res.ColorStateList import android.util.AttributeSet +import android.view.LayoutInflater import android.widget.FrameLayout import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.databinding.ItemPermissionBinding import code.name.monkey.retromusic.extensions.accentOutlineColor -import kotlinx.android.synthetic.main.item_permission.view.* class PermissionItem @JvmOverloads constructor( context: Context, @@ -16,28 +17,34 @@ class PermissionItem @JvmOverloads constructor( defStyleAttr: Int = -1, defStyleRes: Int = -1 ) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) { + private var binding: ItemPermissionBinding + val checkImage get() = binding.checkImage + init { val attributes = context.obtainStyledAttributes(attrs, R.styleable.PermissionItem, 0, 0) - inflate(context, R.layout.item_permission, this) + binding = ItemPermissionBinding.inflate(LayoutInflater.from(context)) + addView(binding.root) - title.text = attributes.getText(R.styleable.PermissionItem_permissionTitle) - summary.text = attributes.getText(R.styleable.PermissionItem_permissionTitleSubTitle) - number.text = attributes.getText(R.styleable.PermissionItem_permissionTitleNumber) - button.text = attributes.getText(R.styleable.PermissionItem_permissionButtonTitle) - button.setIconResource( + binding.title.text = attributes.getText(R.styleable.PermissionItem_permissionTitle) + binding.summary.text = + attributes.getText(R.styleable.PermissionItem_permissionTitleSubTitle) + binding.number.text = attributes.getText(R.styleable.PermissionItem_permissionTitleNumber) + binding.button.text = attributes.getText(R.styleable.PermissionItem_permissionButtonTitle) + binding.button.setIconResource( attributes.getResourceId( R.styleable.PermissionItem_permissionIcon, R.drawable.ic_album ) ) val color = ThemeStore.accentColor(context) - number.backgroundTintList = ColorStateList.valueOf(ColorUtil.withAlpha(color, 0.22f)) + binding.number.backgroundTintList = + ColorStateList.valueOf(ColorUtil.withAlpha(color, 0.22f)) - button.accentOutlineColor() + binding.button.accentOutlineColor() attributes.recycle() } fun setButtonClick(callBack: () -> Unit) { - button.setOnClickListener { callBack.invoke() } + binding.button.setOnClickListener { callBack.invoke() } } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/views/PopupBackground.java b/app/src/main/java/code/name/monkey/retromusic/views/PopupBackground.java index 685c0089..1c4c814e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/PopupBackground.java +++ b/app/src/main/java/code/name/monkey/retromusic/views/PopupBackground.java @@ -27,9 +27,11 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; import android.view.View; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.graphics.drawable.DrawableCompat; + import code.name.monkey.appthemehelper.ThemeStore; import code.name.monkey.retromusic.R; diff --git a/app/src/main/java/code/name/monkey/retromusic/views/ScrollingViewOnApplyWindowInsetsListener.java b/app/src/main/java/code/name/monkey/retromusic/views/ScrollingViewOnApplyWindowInsetsListener.java index 7f2d049a..a11950a5 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/ScrollingViewOnApplyWindowInsetsListener.java +++ b/app/src/main/java/code/name/monkey/retromusic/views/ScrollingViewOnApplyWindowInsetsListener.java @@ -17,8 +17,10 @@ package code.name.monkey.retromusic.views; import android.graphics.Rect; import android.view.View; import android.view.WindowInsets; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; + import me.zhanghai.android.fastscroll.FastScroller; public class ScrollingViewOnApplyWindowInsetsListener implements View.OnApplyWindowInsetsListener { diff --git a/app/src/main/java/code/name/monkey/retromusic/views/SeekArc.java b/app/src/main/java/code/name/monkey/retromusic/views/SeekArc.java index 82b6b546..029ef96e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/SeekArc.java +++ b/app/src/main/java/code/name/monkey/retromusic/views/SeekArc.java @@ -24,6 +24,7 @@ import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; + import code.name.monkey.retromusic.R; /** @@ -206,10 +207,6 @@ public class SeekArc extends View { updateOnTouch(event); break; case MotionEvent.ACTION_UP: - onStopTrackingTouch(); - setPressed(false); - this.getParent().requestDisallowInterceptTouchEvent(false); - break; case MotionEvent.ACTION_CANCEL: onStopTrackingTouch(); setPressed(false); diff --git a/app/src/main/java/code/name/monkey/retromusic/views/SettingListItemView.kt b/app/src/main/java/code/name/monkey/retromusic/views/SettingListItemView.kt index 42831ece..b72ee20f 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/SettingListItemView.kt +++ b/app/src/main/java/code/name/monkey/retromusic/views/SettingListItemView.kt @@ -16,10 +16,10 @@ package code.name.monkey.retromusic.views import android.content.Context import android.graphics.Color import android.util.AttributeSet -import android.view.View +import android.view.LayoutInflater import android.widget.FrameLayout import code.name.monkey.retromusic.R -import kotlinx.android.synthetic.main.list_setting_item_view.view.* +import code.name.monkey.retromusic.databinding.ListSettingItemViewBinding /** * Created by hemanths on 2019-12-10. @@ -30,22 +30,26 @@ class SettingListItemView @JvmOverloads constructor( defStyleAttr: Int = -1, defStyleRes: Int = -1 ) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) { + private var binding: ListSettingItemViewBinding = + ListSettingItemViewBinding.inflate(LayoutInflater.from(context)) + init { - View.inflate(context, R.layout.list_setting_item_view, this) + addView(binding.root) val typedArray = context.obtainStyledAttributes(attrs, R.styleable.SettingListItemView) - icon as ColorIconsImageView + binding.icon if (typedArray.hasValue(R.styleable.SettingListItemView_settingListItemIcon)) { - icon.setImageDrawable(typedArray.getDrawable(R.styleable.SettingListItemView_settingListItemIcon)) + binding.icon.setImageDrawable(typedArray.getDrawable(R.styleable.SettingListItemView_settingListItemIcon)) } - icon.setIconBackgroundColor( + binding.icon.setIconBackgroundColor( typedArray.getColor( R.styleable.SettingListItemView_settingListItemIconColor, Color.WHITE ) ) - title.text = typedArray.getText(R.styleable.SettingListItemView_settingListItemTitle) - text.text = typedArray.getText(R.styleable.SettingListItemView_settingListItemText) + binding.title.text = + typedArray.getText(R.styleable.SettingListItemView_settingListItemTitle) + binding.text.text = typedArray.getText(R.styleable.SettingListItemView_settingListItemText) typedArray.recycle() } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/views/StatusBarMarginFrameLayout.java b/app/src/main/java/code/name/monkey/retromusic/views/StatusBarMarginFrameLayout.java index f4bc8f8f..6a95753f 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/StatusBarMarginFrameLayout.java +++ b/app/src/main/java/code/name/monkey/retromusic/views/StatusBarMarginFrameLayout.java @@ -19,6 +19,7 @@ import android.os.Build; import android.util.AttributeSet; import android.view.WindowInsets; import android.widget.FrameLayout; + import androidx.annotation.NonNull; public class StatusBarMarginFrameLayout extends FrameLayout { diff --git a/app/src/main/java/code/name/monkey/retromusic/views/StatusBarView.java b/app/src/main/java/code/name/monkey/retromusic/views/StatusBarView.java index c149cc3d..21c0dc91 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/StatusBarView.java +++ b/app/src/main/java/code/name/monkey/retromusic/views/StatusBarView.java @@ -18,6 +18,7 @@ import android.content.Context; import android.content.res.Resources; import android.util.AttributeSet; import android.view.View; + import androidx.annotation.NonNull; public class StatusBarView extends View { diff --git a/app/src/main/java/code/name/monkey/retromusic/views/VerticalTextView.java b/app/src/main/java/code/name/monkey/retromusic/views/VerticalTextView.java index 1172bb6a..5c086aad 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/VerticalTextView.java +++ b/app/src/main/java/code/name/monkey/retromusic/views/VerticalTextView.java @@ -19,6 +19,7 @@ import android.graphics.Canvas; import android.text.TextPaint; import android.util.AttributeSet; import android.view.Gravity; + import androidx.appcompat.widget.AppCompatTextView; public class VerticalTextView extends AppCompatTextView { diff --git a/app/src/main/java/code/name/monkey/retromusic/volume/AudioVolumeContentObserver.java b/app/src/main/java/code/name/monkey/retromusic/volume/AudioVolumeContentObserver.java index cea4a85e..eb0eeec5 100644 --- a/app/src/main/java/code/name/monkey/retromusic/volume/AudioVolumeContentObserver.java +++ b/app/src/main/java/code/name/monkey/retromusic/volume/AudioVolumeContentObserver.java @@ -18,6 +18,7 @@ import android.database.ContentObserver; import android.media.AudioManager; import android.net.Uri; import android.os.Handler; + import androidx.annotation.NonNull; public class AudioVolumeContentObserver extends ContentObserver { diff --git a/app/src/main/res/drawable/asld_album.xml b/app/src/main/res/drawable/asld_album.xml new file mode 100644 index 00000000..2a57875a --- /dev/null +++ b/app/src/main/res/drawable/asld_album.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/asld_artist.xml b/app/src/main/res/drawable/asld_artist.xml new file mode 100644 index 00000000..55a8e367 --- /dev/null +++ b/app/src/main/res/drawable/asld_artist.xml @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/asld_face.xml b/app/src/main/res/drawable/asld_face.xml new file mode 100644 index 00000000..63fd8ae4 --- /dev/null +++ b/app/src/main/res/drawable/asld_face.xml @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/asld_folder.xml b/app/src/main/res/drawable/asld_folder.xml new file mode 100644 index 00000000..c8abb3f6 --- /dev/null +++ b/app/src/main/res/drawable/asld_folder.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/asld_guitar.xml b/app/src/main/res/drawable/asld_guitar.xml new file mode 100644 index 00000000..3f214867 --- /dev/null +++ b/app/src/main/res/drawable/asld_guitar.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/asld_heart.xml b/app/src/main/res/drawable/asld_heart.xml new file mode 100644 index 00000000..4433377a --- /dev/null +++ b/app/src/main/res/drawable/asld_heart.xml @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/asld_music_note.xml b/app/src/main/res/drawable/asld_music_note.xml new file mode 100644 index 00000000..7251de26 --- /dev/null +++ b/app/src/main/res/drawable/asld_music_note.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/asld_playlist.xml b/app/src/main/res/drawable/asld_playlist.xml new file mode 100644 index 00000000..2410010e --- /dev/null +++ b/app/src/main/res/drawable/asld_playlist.xml @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/avd_album.xml b/app/src/main/res/drawable/avd_album.xml new file mode 100644 index 00000000..454ae79e --- /dev/null +++ b/app/src/main/res/drawable/avd_album.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/avd_artist.xml b/app/src/main/res/drawable/avd_artist.xml new file mode 100644 index 00000000..47fe7343 --- /dev/null +++ b/app/src/main/res/drawable/avd_artist.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/avd_face.xml b/app/src/main/res/drawable/avd_face.xml new file mode 100644 index 00000000..0e7bb6b4 --- /dev/null +++ b/app/src/main/res/drawable/avd_face.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/avd_favorite.xml b/app/src/main/res/drawable/avd_favorite.xml new file mode 100644 index 00000000..a94d5f6b --- /dev/null +++ b/app/src/main/res/drawable/avd_favorite.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/avd_folder.xml b/app/src/main/res/drawable/avd_folder.xml new file mode 100644 index 00000000..ce8fc679 --- /dev/null +++ b/app/src/main/res/drawable/avd_folder.xml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/avd_guitar.xml b/app/src/main/res/drawable/avd_guitar.xml new file mode 100644 index 00000000..a9ceaf7e --- /dev/null +++ b/app/src/main/res/drawable/avd_guitar.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/avd_music_note.xml b/app/src/main/res/drawable/avd_music_note.xml new file mode 100644 index 00000000..7cb3488e --- /dev/null +++ b/app/src/main/res/drawable/avd_music_note.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/avd_playlist.xml b/app/src/main/res/drawable/avd_playlist.xml new file mode 100644 index 00000000..4a0daede --- /dev/null +++ b/app/src/main/res/drawable/avd_playlist.xml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/avd_unfavorite.xml b/app/src/main/res/drawable/avd_unfavorite.xml new file mode 100644 index 00000000..aae7e036 --- /dev/null +++ b/app/src/main/res/drawable/avd_unfavorite.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_album_artist.xml b/app/src/main/res/drawable/ic_album_artist.xml new file mode 100644 index 00000000..64784443 --- /dev/null +++ b/app/src/main/res/drawable/ic_album_artist.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_baseline.xml b/app/src/main/res/drawable/ic_baseline.xml new file mode 100644 index 00000000..1217c34b --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_favorite_border.xml b/app/src/main/res/drawable/ic_favorite_border.xml index b399e446..9286ddd0 100644 --- a/app/src/main/res/drawable/ic_favorite_border.xml +++ b/app/src/main/res/drawable/ic_favorite_border.xml @@ -2,10 +2,24 @@ - - + android:name="heartbreak" + android:viewportWidth="56" + android:viewportHeight="56"> + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_lyrics.xml b/app/src/main/res/drawable/ic_lyrics.xml new file mode 100644 index 00000000..6a2a3a72 --- /dev/null +++ b/app/src/main/res/drawable/ic_lyrics.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_lyrics_outline.xml b/app/src/main/res/drawable/ic_lyrics_outline.xml new file mode 100644 index 00000000..f1d5843a --- /dev/null +++ b/app/src/main/res/drawable/ic_lyrics_outline.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_pause_outline.xml b/app/src/main/res/drawable/ic_pause_outline.xml new file mode 100644 index 00000000..8d4364c6 --- /dev/null +++ b/app/src/main/res/drawable/ic_pause_outline.xml @@ -0,0 +1,16 @@ + + + + diff --git a/app/src/main/res/drawable/ic_play_arrow_outline.xml b/app/src/main/res/drawable/ic_play_arrow_outline.xml new file mode 100644 index 00000000..3bfc086a --- /dev/null +++ b/app/src/main/res/drawable/ic_play_arrow_outline.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_playlist_remove.xml b/app/src/main/res/drawable/ic_playlist_remove.xml new file mode 100644 index 00000000..1becc971 --- /dev/null +++ b/app/src/main/res/drawable/ic_playlist_remove.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_skip_next_outline.xml b/app/src/main/res/drawable/ic_skip_next_outline.xml new file mode 100644 index 00000000..0c9da3b1 --- /dev/null +++ b/app/src/main/res/drawable/ic_skip_next_outline.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_skip_previous_outline.xml b/app/src/main/res/drawable/ic_skip_previous_outline.xml new file mode 100644 index 00000000..cbfdf33c --- /dev/null +++ b/app/src/main/res/drawable/ic_skip_previous_outline.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_storage.xml b/app/src/main/res/drawable/ic_storage.xml new file mode 100644 index 00000000..a4a57c40 --- /dev/null +++ b/app/src/main/res/drawable/ic_storage.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/lyrics_mask.xml b/app/src/main/res/drawable/lyrics_mask.xml new file mode 100644 index 00000000..e2d5c451 --- /dev/null +++ b/app/src/main/res/drawable/lyrics_mask.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shadow_blur_theme.xml b/app/src/main/res/drawable/shadow_blur_theme.xml new file mode 100644 index 00000000..fad19a4d --- /dev/null +++ b/app/src/main/res/drawable/shadow_blur_theme.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/tab_lyrics_indicator.xml b/app/src/main/res/drawable/tab_lyrics_indicator.xml new file mode 100644 index 00000000..791c672b --- /dev/null +++ b/app/src/main/res/drawable/tab_lyrics_indicator.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/app/src/main/res/layout-land/activity_drive_mode.xml b/app/src/main/res/layout-land/activity_drive_mode.xml index aeef74ed..6a7fcc8c 100644 --- a/app/src/main/res/layout-land/activity_drive_mode.xml +++ b/app/src/main/res/layout-land/activity_drive_mode.xml @@ -176,7 +176,7 @@ - + diff --git a/app/src/main/res/layout-land/fragment_artist_details.xml b/app/src/main/res/layout-land/fragment_artist_details.xml index 1daebee8..4e771de7 100644 --- a/app/src/main/res/layout-land/fragment_artist_details.xml +++ b/app/src/main/res/layout-land/fragment_artist_details.xml @@ -2,7 +2,6 @@ - + diff --git a/app/src/main/res/layout-land/fragment_banner_home.xml b/app/src/main/res/layout-land/fragment_banner_home.xml index 0b13fab3..74920bfd 100644 --- a/app/src/main/res/layout-land/fragment_banner_home.xml +++ b/app/src/main/res/layout-land/fragment_banner_home.xml @@ -15,7 +15,8 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="match_parent" + android:transitionGroup="true"> - - - + android:layout_height="match_parent" + android:layout_marginStart="@dimen/toolbar_margin_horizontal" + android:layout_marginEnd="@dimen/toolbar_margin_horizontal" + android:descendantFocusability="beforeDescendants" + android:focusableInTouchMode="true" + android:overScrollMode="never" + android:transitionGroup="true" + app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> - - - - - - - - - - - - - - - - - - - - + android:orientation="vertical"> + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/fragment_blur.xml b/app/src/main/res/layout-land/fragment_blur.xml index 52efd9c6..6568c6ae 100644 --- a/app/src/main/res/layout-land/fragment_blur.xml +++ b/app/src/main/res/layout-land/fragment_blur.xml @@ -52,7 +52,7 @@ android:layout_height="match_parent" android:layout_gravity="center_horizontal"> - - - - - - - - - \ No newline at end of file + diff --git a/app/src/main/res/layout-land/fragment_flat_player.xml b/app/src/main/res/layout-land/fragment_flat_player.xml index d59f06a2..37ca72b1 100644 --- a/app/src/main/res/layout-land/fragment_flat_player.xml +++ b/app/src/main/res/layout-land/fragment_flat_player.xml @@ -42,7 +42,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - + android:layout_height="match_parent" + android:transitionGroup="true"> @@ -108,6 +111,7 @@ tools:text="@tools:sample/lorem" /> - - - @@ -90,7 +93,7 @@ android:layout_height="match_parent" android:layout_weight="1"> - - - - - + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-xlarge-land/fragment_blur.xml b/app/src/main/res/layout-xlarge-land/fragment_blur.xml index c918471c..c0899dca 100644 --- a/app/src/main/res/layout-xlarge-land/fragment_blur.xml +++ b/app/src/main/res/layout-xlarge-land/fragment_blur.xml @@ -43,7 +43,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/statusBarContainer"> - - - + - + - + - + \ No newline at end of file diff --git a/app/src/main/res/layout/abs_playlists.xml b/app/src/main/res/layout/abs_playlists.xml index 20128735..608b697a 100644 --- a/app/src/main/res/layout/abs_playlists.xml +++ b/app/src/main/res/layout/abs_playlists.xml @@ -37,6 +37,7 @@ android:gravity="center" android:paddingTop="8dp" android:text="@string/history" + android:textAppearance="@style/TextViewNormalCompress" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/v1" /> @@ -72,6 +73,7 @@ android:gravity="center" android:paddingTop="8dp" android:text="@string/last_added" + android:textAppearance="@style/TextViewNormalCompress" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/v2" /> @@ -144,6 +146,7 @@ android:gravity="center" android:paddingTop="8dp" android:text="@string/shuffle" + android:textAppearance="@style/TextViewNormalCompress" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/v4" /> diff --git a/app/src/main/res/layout/activity_bug_report.xml b/app/src/main/res/layout/activity_bug_report.xml index ab45e341..b1216d92 100644 --- a/app/src/main/res/layout/activity_bug_report.xml +++ b/app/src/main/res/layout/activity_bug_report.xml @@ -45,12 +45,14 @@ android:orientation="vertical"> diff --git a/app/src/main/res/layout/activity_drive_mode.xml b/app/src/main/res/layout/activity_drive_mode.xml index 800b5c4a..31841df4 100644 --- a/app/src/main/res/layout/activity_drive_mode.xml +++ b/app/src/main/res/layout/activity_drive_mode.xml @@ -183,7 +183,7 @@ - - - + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index 72796a35..c5cc80e5 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -17,21 +17,6 @@ android:layout_height="match_parent" android:background="?attr/colorSurface"> - - - - - + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_song_tag_editor.xml b/app/src/main/res/layout/activity_song_tag_editor.xml index 6354f13c..e6fbdd7a 100755 --- a/app/src/main/res/layout/activity_song_tag_editor.xml +++ b/app/src/main/res/layout/activity_song_tag_editor.xml @@ -131,7 +131,8 @@ + android:orientation="horizontal" + android:baselineAligned="false"> + android:inputType="textMultiLine" + android:maxLines="15" /> diff --git a/app/src/main/res/layout/activity_user_info.xml b/app/src/main/res/layout/activity_user_info.xml index f9993774..3d5ec19f 100644 --- a/app/src/main/res/layout/activity_user_info.xml +++ b/app/src/main/res/layout/activity_user_info.xml @@ -17,6 +17,7 @@ @@ -33,7 +34,7 @@ android:id="@+id/bannerImage" android:layout_width="0dp" android:layout_height="0dp" - android:layout_margin="16dp" + android:layout_margin="8dp" android:scaleType="centerCrop" app:layout_constraintDimensionRatio="21:10" app:layout_constraintEnd_toEndOf="parent" diff --git a/app/src/main/res/layout/activity_whats_new.xml b/app/src/main/res/layout/activity_whats_new.xml index 6420b1c1..aad2a613 100644 --- a/app/src/main/res/layout/activity_whats_new.xml +++ b/app/src/main/res/layout/activity_whats_new.xml @@ -19,6 +19,17 @@ android:background="?attr/colorSurface"> + + + + diff --git a/app/src/main/res/layout/cast_controller_layout.xml b/app/src/main/res/layout/cast_controller_layout.xml new file mode 100644 index 00000000..1047760d --- /dev/null +++ b/app/src/main/res/layout/cast_controller_layout.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/cast_mini_controller.xml b/app/src/main/res/layout/cast_mini_controller.xml new file mode 100644 index 00000000..a8a4f5c9 --- /dev/null +++ b/app/src/main/res/layout/cast_mini_controller.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_edit_lyrics.xml b/app/src/main/res/layout/dialog_edit_lyrics.xml new file mode 100644 index 00000000..ac767afe --- /dev/null +++ b/app/src/main/res/layout/dialog_edit_lyrics.xml @@ -0,0 +1,39 @@ + + + + + + +