From df37529db80b9bffa4bffb427285a3ddad89e8b3 Mon Sep 17 00:00:00 2001 From: h4h13 Date: Wed, 5 Dec 2018 09:59:55 +0530 Subject: [PATCH] Adding now playing themes and KOTLIN conversion --- app/app.iml | 268 ++-- app/build.gradle | 1 - app/src/main/AndroidManifest.xml | 4 +- app/src/main/assets/retro-changelog.html | 2 +- .../{RetroApplication.kt => App.kt} | 5 +- .../dialogs/AddToPlaylistDialog.java | 84 - .../retromusic/dialogs/AddToPlaylistDialog.kt | 68 + .../dialogs/ClearSmartPlaylistDialog.java | 52 - .../dialogs/ClearSmartPlaylistDialog.kt | 45 + .../dialogs/CreatePlaylistDialog.java | 123 -- .../dialogs/CreatePlaylistDialog.kt | 83 + .../dialogs/DeletePlaylistDialog.java | 102 -- .../dialogs/DeletePlaylistDialog.kt | 70 + .../MainOptionsBottomSheetDialogFragment.java | 167 -- .../MainOptionsBottomSheetDialogFragment.kt | 117 ++ .../dialogs/RemoveFromPlaylistDialog.kt | 40 +- .../dialogs/RenamePlaylistDialog.java | 98 -- .../dialogs/RenamePlaylistDialog.kt | 65 + .../retromusic/glide/ArtistGlideRequest.java | 6 +- .../glide/RetroMusicColoredTarget.kt | 1 - .../retromusic/helper/MusicPlayerRemote.kt | 6 +- .../retromusic/helper/menu/GenreMenuHelper.kt | 16 +- .../helper/menu/PlaylistMenuHelper.kt | 6 +- .../monkey/retromusic/loaders/SongLoader.kt | 2 +- .../name/monkey/retromusic/model/Album.kt | 2 +- .../name/monkey/retromusic/model/Genre.java | 86 + .../name/monkey/retromusic/model/Genre.kt | 26 - .../code/name/monkey/retromusic/model/Song.kt | 4 +- .../mvp/presenter/ArtistDetailsPresenter.java | 7 +- .../NowPlayingScreenPreferenceDialog.java | 6 +- .../retromusic/providers/RepositoryImpl.java | 4 +- .../monkey/retromusic/rest/KogouClient.java | 4 +- .../retromusic/service/MultiPlayer.java | 334 ++++ .../monkey/retromusic/service/MultiPlayer.kt | 333 ---- .../retromusic/service/MusicService.java | 1420 +++++++++++++++++ .../monkey/retromusic/service/MusicService.kt | 1247 --------------- .../ui/activities/AlbumDetailsActivity.kt | 103 +- .../ui/activities/ArtistDetailActivity.java | 458 ------ .../ui/activities/ArtistDetailActivity.kt | 363 +++++ .../ui/activities/ErrorHandlerActivity.java | 2 +- .../ui/activities/GenreDetailsActivity.java | 226 --- .../ui/activities/GenreDetailsActivity.kt | 174 ++ .../retromusic/ui/activities/MainActivity.kt | 141 +- .../ui/activities/PlaylistDetailActivity.java | 309 ---- .../ui/activities/PlaylistDetailActivity.kt | 254 +++ .../ui/activities/ProVersionActivity.java | 6 +- .../ui/activities/SettingsActivity.java | 163 -- .../ui/activities/SettingsActivity.kt | 124 ++ .../ui/activities/WhatsNewActivity.kt | 2 +- .../ui/activities/base/AbsBaseActivity.java | 159 -- .../ui/activities/base/AbsBaseActivity.kt | 18 +- .../base/AbsMusicServiceActivity.kt | 4 +- .../base/AbsSlidingMusicPanelActivity.kt | 31 +- .../ui/activities/base/AbsThemeActivity.kt | 15 +- .../ui/adapter/CollageSongAdapter.java | 17 +- .../ui/adapter/song/AbsOffsetSongAdapter.kt | 2 +- .../song/OrderablePlaylistSongAdapter.kt | 2 +- .../ui/fragments/MiniPlayerFragment.kt | 3 +- .../ui/fragments/NowPlayingScreen.java | 14 +- .../ui/fragments/PlayingQueueFragment.java | 148 -- .../retromusic/ui/fragments/VolumeFragment.kt | 20 +- .../base/AbsPlayerControlsFragment.kt | 9 +- .../ui/fragments/base/AbsPlayerFragment.kt | 6 +- .../fragments/mainactivity/GenreFragment.kt | 6 +- .../fragments/mainactivity/LibraryFragment.kt | 6 +- .../mainactivity/folders/FoldersFragment.java | 59 +- .../mainactivity/home/BannerHomeFragment.kt | 225 ++- .../player/PlayerAlbumCoverFragment.kt | 1 - .../player/adaptive/AdaptiveFragment.kt | 29 +- .../AdaptivePlaybackControlsFragment.kt | 81 +- .../blur/BlurPlaybackControlsFragment.kt | 170 +- .../player/blur/BlurPlayerFragment.kt | 89 +- .../player/cardblur/CardBlurFragment.kt | 151 ++ .../CardBlurPlaybackControlsFragment.kt | 222 +++ .../ui/fragments/player/fit/FitFragment.kt | 110 ++ .../player/fit/FitPlaybackControlsFragment.kt | 271 ++++ .../flat/FlatPlaybackControlsFragment.kt | 230 +++ .../player/flat/FlatPlayerFragment.kt | 128 ++ .../settings/MainSettingsFragment.java | 162 -- .../settings/MainSettingsFragment.kt | 54 + .../settings/NowPlayingSettingsFragment.java | 4 +- .../settings/PersonaizeSettingsFragment.java | 4 +- .../settings/ThemeSettingsFragment.java | 4 +- .../util/CustomArtistImageUtil.java | 20 +- .../retromusic/util/NavigationUtil.java | 5 +- .../retromusic/util/PreferenceUtil.java | 6 +- .../monkey/retromusic/util/RetroUtil.java | 20 +- .../monkey/retromusic/util/SystemUtils.java | 6 +- .../views/MaterialButtonTextColor.java | 25 + app/src/main/res/drawable/navigation_item.xml | 5 + .../res/drawable/navigation_view_item.xml | 7 + app/src/main/res/font/circular_std_black.otf | Bin 74500 -> 0 bytes app/src/main/res/font/circular_std_book.otf | Bin 68940 -> 0 bytes app/src/main/res/font/font.xml | 4 +- app/src/main/res/font/sans_bold.ttf | Bin 0 -> 117916 bytes app/src/main/res/font/sans_regular.ttf | Bin 0 -> 119984 bytes .../layout-land/fragment_card_blur_player.xml | 18 +- .../res/layout-land/fragment_card_player.xml | 13 +- .../res/layout-land/fragment_flat_player.xml | 12 +- .../main/res/layout-land/fragment_home.xml | 23 +- .../main/res/layout-land/fragment_player.xml | 4 +- .../res/layout-xlarge-land/fragment_home.xml | 23 +- .../layout-xlarge-land/fragment_player.xml | 5 +- .../main/res/layout-xlarge/fragment_home.xml | 23 +- .../res/layout-xlarge/fragment_player.xml | 5 +- .../res/layout/fragment_adaptive_player.xml | 6 +- ...ment_adaptive_player_playback_controls.xml | 24 +- .../res/layout/fragment_card_blur_player.xml | 17 +- ...ent_card_blur_player_playback_controls.xml | 133 +- .../main/res/layout/fragment_card_player.xml | 11 +- ...fragment_card_player_playback_controls.xml | 158 +- app/src/main/res/layout/fragment_fit.xml | 8 +- .../layout/fragment_fit_playback_controls.xml | 114 +- .../main/res/layout/fragment_flat_player.xml | 14 +- ...fragment_flat_player_playback_controls.xml | 58 +- app/src/main/res/layout/fragment_full.xml | 2 +- .../main/res/layout/fragment_hmm_player.xml | 3 +- app/src/main/res/layout/fragment_home.xml | 4 +- app/src/main/res/layout/fragment_player.xml | 11 +- .../fragment_player_playback_controls.xml | 130 +- app/src/main/res/layout/fragment_volume.xml | 2 +- .../main/res/menu/bottom_navigation_main.xml | 6 - app/src/main/res/menu/menu_drawer.xml | 29 + app/src/main/res/values/arrays.xml | 2 - .../res/values/ic_launcher_background.xml | 2 - app/src/main/res/values/md_colors.xml | 4 - app/src/main/res/values/strings.xml | 1 + app/src/main/res/values/styles.xml | 21 +- .../main/res/values/swipe_button_attrs.xml | 31 - app/src/sans/res/font/font.xml | 4 + app/src/sans/res/font/product_sans_bold.ttf | Bin 0 -> 55548 bytes 131 files changed, 5398 insertions(+), 5304 deletions(-) rename app/src/main/java/code/name/monkey/retromusic/{RetroApplication.kt => App.kt} (94%) delete mode 100644 app/src/main/java/code/name/monkey/retromusic/dialogs/AddToPlaylistDialog.java create mode 100644 app/src/main/java/code/name/monkey/retromusic/dialogs/AddToPlaylistDialog.kt delete mode 100644 app/src/main/java/code/name/monkey/retromusic/dialogs/ClearSmartPlaylistDialog.java create mode 100644 app/src/main/java/code/name/monkey/retromusic/dialogs/ClearSmartPlaylistDialog.kt delete mode 100644 app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.java create mode 100644 app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.kt delete mode 100644 app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.java create mode 100644 app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.kt delete mode 100644 app/src/main/java/code/name/monkey/retromusic/dialogs/MainOptionsBottomSheetDialogFragment.java create mode 100644 app/src/main/java/code/name/monkey/retromusic/dialogs/MainOptionsBottomSheetDialogFragment.kt delete mode 100644 app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.java create mode 100644 app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/model/Genre.java delete mode 100644 app/src/main/java/code/name/monkey/retromusic/model/Genre.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/service/MultiPlayer.java delete mode 100644 app/src/main/java/code/name/monkey/retromusic/service/MultiPlayer.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/service/MusicService.java delete mode 100644 app/src/main/java/code/name/monkey/retromusic/service/MusicService.kt delete mode 100755 app/src/main/java/code/name/monkey/retromusic/ui/activities/ArtistDetailActivity.java create mode 100755 app/src/main/java/code/name/monkey/retromusic/ui/activities/ArtistDetailActivity.kt delete mode 100644 app/src/main/java/code/name/monkey/retromusic/ui/activities/GenreDetailsActivity.java create mode 100644 app/src/main/java/code/name/monkey/retromusic/ui/activities/GenreDetailsActivity.kt delete mode 100644 app/src/main/java/code/name/monkey/retromusic/ui/activities/PlaylistDetailActivity.java create mode 100644 app/src/main/java/code/name/monkey/retromusic/ui/activities/PlaylistDetailActivity.kt delete mode 100755 app/src/main/java/code/name/monkey/retromusic/ui/activities/SettingsActivity.java create mode 100755 app/src/main/java/code/name/monkey/retromusic/ui/activities/SettingsActivity.kt delete mode 100644 app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsBaseActivity.java delete mode 100644 app/src/main/java/code/name/monkey/retromusic/ui/fragments/PlayingQueueFragment.java create mode 100644 app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/cardblur/CardBlurFragment.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/cardblur/CardBlurPlaybackControlsFragment.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/fit/FitFragment.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/fit/FitPlaybackControlsFragment.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/flat/FlatPlaybackControlsFragment.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/flat/FlatPlayerFragment.kt delete mode 100644 app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/MainSettingsFragment.java create mode 100644 app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/MainSettingsFragment.kt create mode 100644 app/src/main/java/code/name/monkey/retromusic/views/MaterialButtonTextColor.java create mode 100644 app/src/main/res/drawable/navigation_item.xml create mode 100644 app/src/main/res/drawable/navigation_view_item.xml delete mode 100755 app/src/main/res/font/circular_std_black.otf delete mode 100755 app/src/main/res/font/circular_std_book.otf create mode 100755 app/src/main/res/font/sans_bold.ttf create mode 100755 app/src/main/res/font/sans_regular.ttf create mode 100644 app/src/main/res/menu/menu_drawer.xml delete mode 100644 app/src/main/res/values/ic_launcher_background.xml delete mode 100644 app/src/main/res/values/md_colors.xml delete mode 100644 app/src/main/res/values/swipe_button_attrs.xml create mode 100755 app/src/sans/res/font/product_sans_bold.ttf diff --git a/app/app.iml b/app/app.iml index 2566dab7..e41380a5 100644 --- a/app/app.iml +++ b/app/app.iml @@ -8,16 +8,16 @@ - @@ -25,87 +25,90 @@ - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -148,13 +151,16 @@ + + + - + @@ -166,7 +172,6 @@ - @@ -189,87 +194,86 @@ - + - - + + + + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - - - + + + + - - - + + - - - - - + + + + + + + + - - - - + + + + - - - - - + + - + + - - - + + - - + + + + + - - - - - - - - + + + - + + - - - - - + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 32115020..9b55926c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -142,7 +142,6 @@ dependencies { implementation 'com.anjlab.android.iab.v3:library:1.0.44' /*UI Library*/ - implementation 'uk.co.chrisjenx:calligraphy:2.3.0' implementation 'me.zhanghai.android.materialprogressbar:library:1.4.2' implementation 'com.r0adkll:slidableactivity:2.0.6' /*Backend all*/ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ca258b34..f9c70b14 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -14,7 +14,7 @@ + android:windowSoftInputMode="stateVisible"/> diff --git a/app/src/main/assets/retro-changelog.html b/app/src/main/assets/retro-changelog.html index 281b5626..9f5329b3 100644 --- a/app/src/main/assets/retro-changelog.html +++ b/app/src/main/assets/retro-changelog.html @@ -1 +1 @@ -

Version 2.2.100

  • Click new music mix to play songs
  • Gradient image option for gird list
  • Clear button for playing queue
  • Click toolbar (Library) to open options
  • Folder list back button
  • New theme Fit
  • On library click on toolbar for accessing main menu
  • On home click on toolbar for accessing search
  • BottomSheetDialogue is now adaptable to screens, background colour and text size consistency.
  • Removed coloured navigation bar option to making app adapt the primary colour
  • Swipe up gesture for now playing removed, replaced with "tap to open", To achieve transparent navigation bar for desired themes.
  • Improved tablet UI and home screen by adding suggestions toggle banner issues.
  • Improving lyrics page

FAQ's

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

\ No newline at end of file +

Version 2.2.100

  • Click new music mix to play songs
  • Gradient image option for gird list
  • Clear button for playing queue
  • Click toolbar (Library) to open options
  • Folder list back button
  • New theme Fit
  • On library click on toolbar for accessing main menu
  • On home click on toolbar for accessing search
  • BottomSheetDialogue is now adaptable to screens, background colour and text size consistency.
  • Removed coloured navigation bar option to making app adapt the primary colour
  • Swipe up gesture for now playing removed, replaced with "tap to open", To achieve transparent navigation bar for desired themes.
  • Improved tablet UI and home screen by adding suggestions toggle banner issues.
  • Improving lyrics page

FAQ's

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

\ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/RetroApplication.kt b/app/src/main/java/code/name/monkey/retromusic/App.kt similarity index 94% rename from app/src/main/java/code/name/monkey/retromusic/RetroApplication.kt rename to app/src/main/java/code/name/monkey/retromusic/App.kt index 176e6a03..0c3d590d 100644 --- a/app/src/main/java/code/name/monkey/retromusic/RetroApplication.kt +++ b/app/src/main/java/code/name/monkey/retromusic/App.kt @@ -6,9 +6,8 @@ import code.name.monkey.appthemehelper.ThemeStore import com.anjlab.android.iab.v3.BillingProcessor import com.anjlab.android.iab.v3.TransactionDetails import com.bumptech.glide.Glide -import uk.co.chrisjenx.calligraphy.CalligraphyConfig -class RetroApplication : MultiDexApplication() { +class App : MultiDexApplication() { lateinit var billingProcessor: BillingProcessor @@ -67,7 +66,7 @@ class RetroApplication : MultiDexApplication() { const val PRO_VERSION_PRODUCT_ID = "pro_version" - lateinit var instance: RetroApplication + lateinit var instance: App private set val context: Context diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/AddToPlaylistDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/AddToPlaylistDialog.java deleted file mode 100644 index 2a8cfa7c..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/AddToPlaylistDialog.java +++ /dev/null @@ -1,84 +0,0 @@ -package code.name.monkey.retromusic.dialogs; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import java.util.ArrayList; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.recyclerview.widget.DefaultItemAnimator; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import butterknife.BindView; -import butterknife.ButterKnife; -import butterknife.OnClick; -import code.name.monkey.appthemehelper.ThemeStore; -import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.loaders.PlaylistLoader; -import code.name.monkey.retromusic.model.Playlist; -import code.name.monkey.retromusic.model.Song; -import code.name.monkey.retromusic.ui.adapter.playlist.AddToPlaylist; -import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment; - -/** - * @author Karim Abou Zeid (kabouzeid), Aidan Follestad (afollestad) - */ -public class AddToPlaylistDialog extends RoundedBottomSheetDialogFragment { - - @BindView(R.id.playlists) - RecyclerView playlist; - - @BindView(R.id.title) - TextView title; - - @NonNull - public static AddToPlaylistDialog create(Song song) { - ArrayList list = new ArrayList<>(); - list.add(song); - return create(list); - } - - @NonNull - public static AddToPlaylistDialog create(ArrayList songs) { - AddToPlaylistDialog dialog = new AddToPlaylistDialog(); - Bundle args = new Bundle(); - args.putParcelableArrayList("songs", songs); - dialog.setArguments(args); - return dialog; - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - View layout = inflater.inflate(R.layout.dialog_add_to_playlist, container, false); - ButterKnife.bind(this, layout); - return layout; - } - - @SuppressWarnings("ConstantConditions") - @OnClick(R.id.action_add_playlist) - void newPlaylist() { - final ArrayList songs = getArguments().getParcelableArrayList("songs"); - CreatePlaylistDialog.create(songs) - .show(getActivity().getSupportFragmentManager(), "ADD_TO_PLAYLIST"); - dismiss(); - } - - @SuppressWarnings("ConstantConditions") - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - title.setTextColor(ThemeStore.textColorPrimary(getContext())); - final ArrayList songs = getArguments().getParcelableArrayList("songs"); - final ArrayList playlists = PlaylistLoader.INSTANCE.getAllPlaylists(getActivity()).blockingFirst(); - final AddToPlaylist playlistAdapter = new AddToPlaylist(getActivity(), playlists, R.layout.item_playlist, songs, getDialog()); - playlist.setLayoutManager(new LinearLayoutManager(getContext())); - playlist.setItemAnimator(new DefaultItemAnimator()); - playlist.setAdapter(playlistAdapter); - } -} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/AddToPlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/AddToPlaylistDialog.kt new file mode 100644 index 00000000..b8b68391 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/AddToPlaylistDialog.kt @@ -0,0 +1,68 @@ +package code.name.monkey.retromusic.dialogs + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DefaultItemAnimator +import androidx.recyclerview.widget.LinearLayoutManager +import butterknife.ButterKnife +import code.name.monkey.appthemehelper.ThemeStore +import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.loaders.PlaylistLoader +import code.name.monkey.retromusic.model.Song +import code.name.monkey.retromusic.ui.adapter.playlist.AddToPlaylist +import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment +import kotlinx.android.synthetic.main.dialog_add_to_playlist.* +import java.util.* + +/** + * @author Karim Abou Zeid (kabouzeid), Aidan Follestad (afollestad) + */ +class AddToPlaylistDialog : RoundedBottomSheetDialogFragment() { + + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + val layout = inflater.inflate(R.layout.dialog_add_to_playlist, container, false) + ButterKnife.bind(this, layout) + return layout + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + actionAddPlaylist.setOnClickListener { + val songs = arguments!!.getParcelableArrayList("songs") + CreatePlaylistDialog.create(songs).show(activity!!.supportFragmentManager, "ADD_TO_PLAYLIST") + dismiss() + } + + bannerTitle.setTextColor(ThemeStore.textColorPrimary(context!!)) + val songs = arguments!!.getParcelableArrayList("songs") + val playlists = PlaylistLoader.getAllPlaylists(activity!!).blockingFirst() + val playlistAdapter = AddToPlaylist(activity!!, playlists, R.layout.item_playlist, songs!!, dialog) + recyclerView.apply { + layoutManager = LinearLayoutManager(context) + itemAnimator = DefaultItemAnimator() + adapter = playlistAdapter + } + } + + companion object { + + fun create(song: Song): AddToPlaylistDialog { + val list = ArrayList() + list.add(song) + return create(list) + } + + fun create(songs: ArrayList): AddToPlaylistDialog { + val dialog = AddToPlaylistDialog() + val args = Bundle() + args.putParcelableArrayList("songs", songs) + dialog.arguments = args + return dialog + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/ClearSmartPlaylistDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/ClearSmartPlaylistDialog.java deleted file mode 100644 index 735b0f9d..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/ClearSmartPlaylistDialog.java +++ /dev/null @@ -1,52 +0,0 @@ -package code.name.monkey.retromusic.dialogs; - -import android.app.Dialog; -import android.os.Bundle; -import android.text.Html; - -import com.afollestad.materialdialogs.DialogAction; -import com.afollestad.materialdialogs.MaterialDialog; - -import androidx.annotation.NonNull; -import androidx.fragment.app.DialogFragment; -import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist; - - -public class ClearSmartPlaylistDialog extends DialogFragment { - - @NonNull - public static ClearSmartPlaylistDialog create(AbsSmartPlaylist playlist) { - ClearSmartPlaylistDialog dialog = new ClearSmartPlaylistDialog(); - Bundle args = new Bundle(); - args.putParcelable("playlist", playlist); - dialog.setArguments(args); - return dialog; - } - - @NonNull - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - //noinspection unchecked - final AbsSmartPlaylist playlist = getArguments().getParcelable("playlist"); - int title = R.string.clear_playlist_title; - //noinspection ConstantConditions - CharSequence content = Html.fromHtml(getString(R.string.clear_playlist_x, playlist.name)); - - return new MaterialDialog.Builder(getActivity()) - .title(title) - .content(content) - .positiveText(R.string.clear_action) - .negativeText(android.R.string.cancel) - .onPositive(new MaterialDialog.SingleButtonCallback() { - @Override - public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { - if (getActivity() == null) { - return; - } - playlist.clear(getActivity()); - } - }) - .build(); - } -} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/ClearSmartPlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/ClearSmartPlaylistDialog.kt new file mode 100644 index 00000000..500d95dc --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/ClearSmartPlaylistDialog.kt @@ -0,0 +1,45 @@ +package code.name.monkey.retromusic.dialogs + +import android.app.Dialog +import android.os.Bundle +import android.text.Html +import androidx.fragment.app.DialogFragment +import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist +import com.afollestad.materialdialogs.MaterialDialog + + +class ClearSmartPlaylistDialog : DialogFragment() { + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + + val playlist = arguments!!.getParcelable("playlist") + val title = R.string.clear_playlist_title + + val content = Html.fromHtml(getString(R.string.clear_playlist_x, playlist!!.name)) + + return MaterialDialog.Builder(activity!!) + .title(title) + .content(content) + .positiveText(R.string.clear_action) + .negativeText(android.R.string.cancel) + .onPositive { _, _ -> + if (activity == null) { + return@onPositive + } + playlist.clear(activity) + } + .build() + } + + companion object { + + fun create(playlist: AbsSmartPlaylist): ClearSmartPlaylistDialog { + val dialog = ClearSmartPlaylistDialog() + val args = Bundle() + args.putParcelable("playlist", playlist) + dialog.arguments = args + return dialog + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.java deleted file mode 100644 index b1b7cf45..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.java +++ /dev/null @@ -1,123 +0,0 @@ -package code.name.monkey.retromusic.dialogs; - -import android.content.res.ColorStateList; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import com.google.android.material.button.MaterialButton; -import com.google.android.material.textfield.TextInputEditText; -import com.google.android.material.textfield.TextInputLayout; - -import java.util.ArrayList; -import java.util.Objects; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import butterknife.BindView; -import butterknife.ButterKnife; -import butterknife.OnClick; -import code.name.monkey.appthemehelper.ThemeStore; -import code.name.monkey.appthemehelper.util.MaterialUtil; -import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.model.Song; -import code.name.monkey.retromusic.util.PlaylistsUtil; -import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment; - -/** - * @author Karim Abou Zeid (kabouzeid), Aidan Follestad (afollestad) - */ -public class CreatePlaylistDialog extends RoundedBottomSheetDialogFragment { - - @BindView(R.id.option_1) - TextInputEditText playlistName; - - @BindView(R.id.action_new_playlist) - TextInputLayout textInputLayout; - - @BindView(R.id.action_cancel) - MaterialButton actionCancel; - - @BindView(R.id.action_create) - MaterialButton actionCreate; - - @BindView(R.id.title) - TextView title; - - @NonNull - public static CreatePlaylistDialog create() { - return create((Song) null); - } - - @NonNull - public static CreatePlaylistDialog create(@Nullable Song song) { - ArrayList list = new ArrayList<>(); - if (song != null) { - list.add(song); - } - return create(list); - } - - @NonNull - public static CreatePlaylistDialog create(ArrayList songs) { - CreatePlaylistDialog dialog = new CreatePlaylistDialog(); - Bundle args = new Bundle(); - args.putParcelableArrayList("songs", songs); - dialog.setArguments(args); - return dialog; - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View layout = inflater.inflate(R.layout.dialog_create_playlist, container, false); - ButterKnife.bind(this, layout); - return layout; - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - int accentColor = ThemeStore.accentColor(Objects.requireNonNull(getContext())); - - MaterialUtil.setTint(actionCreate, true); - - MaterialUtil.setTint(actionCancel, false); - - MaterialUtil.setTint(textInputLayout, true); - - - playlistName.setHintTextColor(ColorStateList.valueOf(accentColor)); - playlistName.setTextColor(ThemeStore.textColorPrimary(getContext())); - - title.setTextColor(ThemeStore.textColorPrimary(getContext())); - } - - @OnClick({R.id.action_cancel, R.id.action_create}) - void actions(View view) { - switch (view.getId()) { - case R.id.action_cancel: - dismiss(); - break; - case R.id.action_create: - if (getActivity() == null) { - return; - } - if (!playlistName.getText().toString().trim().isEmpty()) { - final int playlistId = PlaylistsUtil - .createPlaylist(getActivity(), playlistName.getText().toString()); - if (playlistId != -1 && getActivity() != null) { - //noinspection unchecked - ArrayList songs = getArguments().getParcelableArrayList("songs"); - if (songs != null) { - PlaylistsUtil.addToPlaylist(getActivity(), songs, playlistId, true); - } - } - } - break; - } - dismiss(); - } -} \ 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 new file mode 100644 index 00000000..f2245eba --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.kt @@ -0,0 +1,83 @@ +package code.name.monkey.retromusic.dialogs + +import android.content.Context +import android.content.res.ColorStateList +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import butterknife.ButterKnife +import code.name.monkey.appthemehelper.ThemeStore +import code.name.monkey.appthemehelper.util.MaterialUtil +import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.model.Song +import code.name.monkey.retromusic.util.PlaylistsUtil +import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment +import kotlinx.android.synthetic.main.dialog_playlist.* +import java.util.* + +/** + * @author Karim Abou Zeid (kabouzeid), Aidan Follestad (afollestad) + */ +class CreatePlaylistDialog : RoundedBottomSheetDialogFragment() { + + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val layout = inflater.inflate(R.layout.dialog_playlist, container, false) + ButterKnife.bind(this, layout) + return layout + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val accentColor = ThemeStore.accentColor(Objects.requireNonNull(context)) + + MaterialUtil.setTint(actionCreate, true) + MaterialUtil.setTint(actionCancel, false) + MaterialUtil.setTint(actionNewPlaylistContainer, true) + + actionNewPlaylist.setHintTextColor(ColorStateList.valueOf(accentColor)) + actionNewPlaylist.setTextColor(ThemeStore.textColorPrimary(context!!)) + bannerTitle.setTextColor(ThemeStore.textColorPrimary(context!!)) + + + actionCancel.setOnClickListener { dismiss() } + actionCreate.setOnClickListener { + if (activity == null) { + return@setOnClickListener + } + if (!actionNewPlaylist!!.text!!.toString().trim { it <= ' ' }.isEmpty()) { + val playlistId = PlaylistsUtil + .createPlaylist(activity!!, actionNewPlaylist!!.text!!.toString()) + if (playlistId != -1 && activity != null) { + + val songs = arguments!!.getParcelableArrayList("songs") + if (songs != null) { + PlaylistsUtil.addToPlaylist(activity!!, songs, playlistId, true) + } + } + } + dismiss() + } + } + + companion object { + + @JvmOverloads + fun create(song: Song? = null): CreatePlaylistDialog { + val list = ArrayList() + if (song != null) { + list.add(song) + } + return create(list) + } + + fun create(songs: ArrayList): CreatePlaylistDialog { + val dialog = CreatePlaylistDialog() + val args = Bundle() + args.putParcelableArrayList("songs", songs) + dialog.arguments = args + return dialog + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.java deleted file mode 100644 index a7681518..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.java +++ /dev/null @@ -1,102 +0,0 @@ -package code.name.monkey.retromusic.dialogs; - -import android.os.Bundle; -import android.text.Html; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import com.google.android.material.button.MaterialButton; - -import java.util.ArrayList; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import butterknife.BindView; -import butterknife.ButterKnife; -import butterknife.OnClick; -import code.name.monkey.appthemehelper.ThemeStore; -import code.name.monkey.appthemehelper.util.MaterialUtil; -import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.model.Playlist; -import code.name.monkey.retromusic.util.PlaylistsUtil; -import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment; - - -public class DeletePlaylistDialog extends RoundedBottomSheetDialogFragment { - - @BindView(R.id.action_delete) - MaterialButton actionDelete; - - @BindView(R.id.title) - TextView title; - - @BindView(R.id.action_cancel) - MaterialButton actionCancel; - - @NonNull - public static DeletePlaylistDialog create(Playlist playlist) { - ArrayList list = new ArrayList<>(); - list.add(playlist); - return create(list); - } - - @NonNull - public static DeletePlaylistDialog create(ArrayList playlists) { - DeletePlaylistDialog dialog = new DeletePlaylistDialog(); - Bundle args = new Bundle(); - args.putParcelableArrayList("playlists", playlists); - dialog.setArguments(args); - return dialog; - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - View layout = inflater.inflate(R.layout.dialog_delete, container, false); - ButterKnife.bind(this, layout); - return layout; - } - - @SuppressWarnings("ConstantConditions") - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - //noinspection unchecked - final ArrayList playlists = getArguments().getParcelableArrayList("playlists"); - int title; - CharSequence content; - //noinspection ConstantConditions - if (playlists.size() > 1) { - content = Html.fromHtml(getString(R.string.delete_x_playlists, playlists.size())); - } else { - content = Html.fromHtml(getString(R.string.delete_playlist_x, playlists.get(0).name)); - } - this.title.setText(content); - this.title.setTextColor(ThemeStore.textColorPrimary(getContext())); - - actionDelete.setText(R.string.action_delete); - - MaterialUtil.setTint(actionDelete, true); - MaterialUtil.setTint(actionCancel, false); - - - } - - @OnClick({R.id.action_cancel, R.id.action_delete}) - void actions(View view) { - final ArrayList playlists = getArguments().getParcelableArrayList("playlists"); - switch (view.getId()) { - case R.id.action_delete: - if (getActivity() == null) - return; - PlaylistsUtil.deletePlaylists(getActivity(), playlists); - break; - default: - } - dismiss(); - } - -} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.kt new file mode 100644 index 00000000..78a2969b --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.kt @@ -0,0 +1,70 @@ +package code.name.monkey.retromusic.dialogs + +import android.os.Bundle +import android.text.Html +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import butterknife.ButterKnife +import code.name.monkey.appthemehelper.ThemeStore +import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.model.Playlist +import code.name.monkey.retromusic.util.PlaylistsUtil +import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment +import kotlinx.android.synthetic.main.dialog_remove_from_playlist.* +import java.util.* + + +class DeletePlaylistDialog : RoundedBottomSheetDialogFragment() { + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + val layout = inflater.inflate(R.layout.dialog_remove_from_playlist, container, false) + ButterKnife.bind(this, layout) + return layout + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val playlists = arguments!!.getParcelableArrayList("playlists") + val content: CharSequence + + content = if (playlists!!.size > 1) { + Html.fromHtml(getString(R.string.delete_x_playlists, playlists.size)) + } else { + Html.fromHtml(getString(R.string.delete_playlist_x, playlists[0].name)) + } + bannerTitle.text = content + bannerTitle.setTextColor(ThemeStore.textColorPrimary(context!!)) + + actionRemove.setText(R.string.action_delete) + actionRemove.setTextColor(ThemeStore.textColorSecondary(context!!)) + actionCancel.setTextColor(ThemeStore.textColorSecondary(context!!)) + + actionCancel.setOnClickListener { dismiss() } + actionRemove.setOnClickListener { + PlaylistsUtil.deletePlaylists(activity!!, playlists) + dismiss() + } + } + + + companion object { + + fun create(playlist: Playlist): DeletePlaylistDialog { + val list = ArrayList() + list.add(playlist) + return create(list) + } + + fun create(playlist: ArrayList): DeletePlaylistDialog { + val dialog = DeletePlaylistDialog() + val args = Bundle() + args.putParcelableArrayList("playlist", playlist) + dialog.arguments = args + return dialog + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/MainOptionsBottomSheetDialogFragment.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/MainOptionsBottomSheetDialogFragment.java deleted file mode 100644 index 0374f023..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/MainOptionsBottomSheetDialogFragment.java +++ /dev/null @@ -1,167 +0,0 @@ -package code.name.monkey.retromusic.dialogs; - -import android.content.res.ColorStateList; -import android.graphics.Bitmap; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.google.android.material.button.MaterialButton; - -import java.io.File; -import java.util.Calendar; -import java.util.List; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.widget.AppCompatTextView; -import androidx.core.content.ContextCompat; -import butterknife.BindView; -import butterknife.BindViews; -import butterknife.ButterKnife; -import butterknife.OnClick; -import code.name.monkey.appthemehelper.ThemeStore; -import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.RetroApplication; -import code.name.monkey.retromusic.ui.activities.MainActivity; -import code.name.monkey.retromusic.ui.fragments.mainactivity.folders.FoldersFragment; -import code.name.monkey.retromusic.util.Compressor; -import code.name.monkey.retromusic.util.NavigationUtil; -import code.name.monkey.retromusic.util.PreferenceUtil; -import code.name.monkey.retromusic.views.CircularImageView; -import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.CompositeDisposable; -import io.reactivex.schedulers.Schedulers; - -import static code.name.monkey.retromusic.Constants.USER_PROFILE; - -public class MainOptionsBottomSheetDialogFragment extends RoundedBottomSheetDialogFragment { - - private static final String TAG = "MainOptionsBottomSheetD"; - private static ButterKnife.Setter textColor = (view, value, index) -> view.setTextColor(ColorStateList.valueOf(value)); - - - @BindViews({R.id.action_folders, R.id.action_about, R.id.action_buy_pro, R.id.action_rate, - R.id.action_sleep_timer}) - List materialButtons; - - @BindView(R.id.user_image_bottom) - CircularImageView userImageBottom; - - @BindView(R.id.title_welcome) - AppCompatTextView titleWelcome; - - @BindView(R.id.text) - AppCompatTextView text; - - private CompositeDisposable disposable = new CompositeDisposable(); - - public static MainOptionsBottomSheetDialogFragment newInstance(int selected_id) { - Bundle bundle = new Bundle(); - bundle.putInt("selected_id", selected_id); - MainOptionsBottomSheetDialogFragment fragment = new MainOptionsBottomSheetDialogFragment(); - fragment.setArguments(bundle); - return fragment; - } - - public static MainOptionsBottomSheetDialogFragment newInstance() { - return new MainOptionsBottomSheetDialogFragment(); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - disposable.clear(); - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View layout = inflater.inflate(R.layout.fragment_main_options, container, false); - ButterKnife.bind(this, layout); - layout.findViewById(R.id.action_buy_pro).setVisibility(RetroApplication.Companion.isProVersion() ? View.GONE : View.VISIBLE); - //ButterKnife.apply(materialButtons, textColor, ThemeStore.textColorPrimary(getContext())); - return layout; - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - text.setTextColor(ThemeStore.textColorSecondary(getContext())); - titleWelcome.setTextColor(ThemeStore.textColorPrimary(getContext())); - titleWelcome.setText(String.format("%s %s!", getTimeOfTheDay(), PreferenceUtil.getInstance().getUserName())); - loadImageFromStorage(); - } - - @OnClick({R.id.action_folders, R.id.user_info_container, R.id.action_settings, R.id.action_sleep_timer, R.id.action_rate, - R.id.action_buy_pro, R.id.action_about}) - void onClick(View view) { - MainActivity mainActivity = (MainActivity) getActivity(); - if (mainActivity == null) { - return; - } - switch (view.getId()) { - case R.id.action_folders: - mainActivity.setCurrentFragment(FoldersFragment.newInstance(getContext()), true, FoldersFragment.TAG); - break; - case R.id.action_settings: - NavigationUtil.goToSettings(mainActivity); - break; - case R.id.action_about: - NavigationUtil.goToAbout(mainActivity); - break; - case R.id.action_buy_pro: - NavigationUtil.goToProVersion(mainActivity); - break; - case R.id.action_sleep_timer: - if (getFragmentManager() != null) { - new SleepTimerDialog().show(getFragmentManager(), TAG); - } - break; - case R.id.user_info_container: - NavigationUtil.goToUserInfo(getActivity()); - break; - case R.id.action_rate: - NavigationUtil.goToPlayStore(mainActivity); - break; - } - dismiss(); - } - - private String getTimeOfTheDay() { - String message = getString(R.string.title_good_day); - Calendar c = Calendar.getInstance(); - int timeOfDay = c.get(Calendar.HOUR_OF_DAY); - - if (timeOfDay >= 0 && timeOfDay < 6) { - message = getString(R.string.title_good_night); - } else if (timeOfDay >= 6 && timeOfDay < 12) { - message = getString(R.string.title_good_morning); - } else if (timeOfDay >= 12 && timeOfDay < 16) { - message = getString(R.string.title_good_afternoon); - } else if (timeOfDay >= 16 && timeOfDay < 20) { - message = getString(R.string.title_good_evening); - } else if (timeOfDay >= 20 && timeOfDay < 24) { - message = getString(R.string.title_good_night); - } - return message; - } - - private void loadImageFromStorage() { - //noinspection ConstantConditions - disposable.add(new Compressor(getContext()) - .setMaxHeight(300) - .setMaxWidth(300) - .setQuality(75) - .setCompressFormat(Bitmap.CompressFormat.WEBP) - .compressToBitmapAsFlowable( - new File(PreferenceUtil.getInstance().getProfileImage(),USER_PROFILE)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(userImageBottom::setImageBitmap, - throwable -> userImageBottom.setImageDrawable(ContextCompat - .getDrawable(getContext(), R.drawable.ic_person_flat)))); - } -} diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/MainOptionsBottomSheetDialogFragment.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/MainOptionsBottomSheetDialogFragment.kt new file mode 100644 index 00000000..50f76b85 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/MainOptionsBottomSheetDialogFragment.kt @@ -0,0 +1,117 @@ +package code.name.monkey.retromusic.dialogs + +import android.graphics.Bitmap +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.content.ContextCompat +import code.name.monkey.appthemehelper.ThemeStore +import code.name.monkey.retromusic.Constants.USER_PROFILE +import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.ui.activities.MainActivity +import code.name.monkey.retromusic.util.Compressor +import code.name.monkey.retromusic.util.NavigationUtil +import code.name.monkey.retromusic.util.PreferenceUtil +import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.schedulers.Schedulers +import kotlinx.android.synthetic.main.fragment_main_options.* +import java.io.File +import java.util.* + +class MainOptionsBottomSheetDialogFragment : RoundedBottomSheetDialogFragment(), View.OnClickListener { + + private val disposable = CompositeDisposable() + + private val timeOfTheDay: String + get() { + var message = getString(R.string.title_good_day) + val c = Calendar.getInstance() + val timeOfDay = c.get(Calendar.HOUR_OF_DAY) + + when (timeOfDay) { + in 0..5 -> message = getString(R.string.title_good_night) + in 6..11 -> message = getString(R.string.title_good_morning) + in 12..15 -> message = getString(R.string.title_good_afternoon) + in 16..19 -> message = getString(R.string.title_good_evening) + in 20..23 -> message = getString(R.string.title_good_night) + } + return message + } + + override fun onDestroyView() { + super.onDestroyView() + disposable.clear() + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_main_options, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + text!!.setTextColor(ThemeStore.textColorSecondary(context!!)) + titleWelcome!!.setTextColor(ThemeStore.textColorPrimary(context!!)) + titleWelcome!!.text = String.format("%s %s!", timeOfTheDay, PreferenceUtil.getInstance().userName) + loadImageFromStorage() + + actionSettings.setOnClickListener(this) + actionAbout.setOnClickListener(this) + actionSleepTimer.setOnClickListener(this) + userInfoContainer.setOnClickListener(this) + actionRate.setOnClickListener(this) + } + + + override fun onClick(view: View) { + val mainActivity = activity as MainActivity? ?: return + when (view.id) { + R.id.actionSettings -> NavigationUtil.goToSettings(mainActivity) + R.id.actionAbout -> NavigationUtil.goToAbout(mainActivity) + R.id.actionSleepTimer -> if (fragmentManager != null) { + SleepTimerDialog().show(fragmentManager!!, TAG) + } + R.id.userInfoContainer -> NavigationUtil.goToUserInfo(activity!!) + R.id.actionRate -> NavigationUtil.goToPlayStore(mainActivity) + } + dismiss() + } + + private fun loadImageFromStorage() { + + disposable.add(Compressor(context!!) + .setMaxHeight(300) + .setMaxWidth(300) + .setQuality(75) + .setCompressFormat(Bitmap.CompressFormat.WEBP) + .compressToBitmapAsFlowable( + File(PreferenceUtil.getInstance().profileImage, USER_PROFILE)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ userImage!!.setImageBitmap(it) }, { + userImage!!.setImageDrawable(ContextCompat + .getDrawable(context!!, R.drawable.ic_person_flat)) + }, { + + })) + } + + companion object { + + private const val TAG: String = "MainOptionsBottomSheetD" + + fun newInstance(selected_id: Int): MainOptionsBottomSheetDialogFragment { + val bundle = Bundle() + bundle.putInt("selected_id", selected_id) + val fragment = MainOptionsBottomSheetDialogFragment() + fragment.arguments = bundle + return fragment + } + + fun newInstance(): MainOptionsBottomSheetDialogFragment { + return MainOptionsBottomSheetDialogFragment() + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveFromPlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveFromPlaylistDialog.kt index 38726666..94988c5a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveFromPlaylistDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveFromPlaylistDialog.kt @@ -6,40 +6,17 @@ import android.text.Html import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.TextView -import butterknife.BindView import butterknife.ButterKnife -import butterknife.OnClick import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.retromusic.R import code.name.monkey.retromusic.model.PlaylistSong import code.name.monkey.retromusic.util.PlaylistsUtil import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment +import kotlinx.android.synthetic.main.dialog_remove_from_playlist.* import java.util.* class RemoveFromPlaylistDialog : RoundedBottomSheetDialogFragment() { - @BindView(R.id.action_remove) - internal var remove: TextView? = null - - @BindView(R.id.title) - internal var title: TextView? = null - - @BindView(R.id.action_cancel) - internal var cancel: TextView? = null - - @OnClick(R.id.action_cancel, R.id.action_remove) - internal fun actions(view: View) { - val songs = arguments!!.getParcelableArrayList("songs") - when (view.id) { - R.id.action_remove -> { - if (activity == null) - return - PlaylistsUtil.removeFromPlaylist(activity!!, songs!!) - } - } - dismiss() - } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val layout = inflater.inflate(R.layout.dialog_remove_from_playlist, container, false) @@ -64,12 +41,17 @@ class RemoveFromPlaylistDialog : RoundedBottomSheetDialogFragment() { title = R.string.remove_song_from_playlist_title content = Html.fromHtml(getString(R.string.remove_song_x_from_playlist, songs!![0].title)) } - this.remove!!.text = content - this.title!!.setText(title) + actionRemove.text = content + bannerTitle.setText(title) - this.title!!.setTextColor(ThemeStore.textColorPrimary(context!!)) - this.remove!!.setTextColor(ThemeStore.textColorSecondary(context!!)) - this.cancel!!.setTextColor(ThemeStore.textColorSecondary(context!!)) + bannerTitle.setTextColor(ThemeStore.textColorPrimary(context!!)) + actionRemove.setTextColor(ThemeStore.textColorSecondary(context!!)) + actionCancel.setTextColor(ThemeStore.textColorSecondary(context!!)) + + actionRemove.setOnClickListener { + PlaylistsUtil.removeFromPlaylist(activity!!, songs) + } + actionCancel.setOnClickListener { dismiss() } } companion object { diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.java deleted file mode 100644 index 19d32f6c..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.java +++ /dev/null @@ -1,98 +0,0 @@ -package code.name.monkey.retromusic.dialogs; - -import android.content.res.ColorStateList; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import com.google.android.material.button.MaterialButton; -import com.google.android.material.textfield.TextInputEditText; -import com.google.android.material.textfield.TextInputLayout; - -import java.util.Objects; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import butterknife.BindView; -import butterknife.ButterKnife; -import butterknife.OnClick; -import code.name.monkey.appthemehelper.ThemeStore; -import code.name.monkey.appthemehelper.util.MaterialUtil; -import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.util.PlaylistsUtil; -import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment; - -public class RenamePlaylistDialog extends RoundedBottomSheetDialogFragment { - - @BindView(R.id.title) - TextView title; - - @BindView(R.id.option_1) - TextInputEditText playlistName; - - @BindView(R.id.action_new_playlist) - TextInputLayout textInputLayout; - - @BindView(R.id.action_cancel) - MaterialButton actionCancel; - - @BindView(R.id.action_rename) - MaterialButton rename; - - @NonNull - public static RenamePlaylistDialog create(long playlistId) { - RenamePlaylistDialog dialog = new RenamePlaylistDialog(); - Bundle args = new Bundle(); - args.putLong("playlist_id", playlistId); - dialog.setArguments(args); - return dialog; - } - - @OnClick({R.id.action_cancel, R.id.action_rename}) - void actions(View view) { - switch (view.getId()) { - case R.id.action_cancel: - dismiss(); - break; - case R.id.action_rename: - if (!playlistName.toString().trim().equals("")) { - long playlistId = getArguments().getLong("playlist_id"); - PlaylistsUtil.renamePlaylist(getActivity(), playlistId, - playlistName.getText().toString()); - } - break; - } - dismiss(); - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - View layout = inflater.inflate(R.layout.dialog_playlist_rename, container, false); - ButterKnife.bind(this, layout); - return layout; - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - int accentColor = ThemeStore.accentColor(Objects.requireNonNull(getContext())); - MaterialUtil.setTint(rename,true); - MaterialUtil.setTint(actionCancel,false); - MaterialUtil.setTint(textInputLayout,false); - - playlistName.setHintTextColor(ColorStateList.valueOf(accentColor)); - playlistName.setTextColor(ThemeStore.textColorPrimary(getContext())); - - title.setTextColor(ThemeStore.textColorPrimary(getContext())); - - long playlistId = 0; - if (getArguments() != null) { - playlistId = getArguments().getLong("playlist_id"); - } - playlistName.setText(PlaylistsUtil.getNameForPlaylist(getActivity(), playlistId)); - } -} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.kt new file mode 100644 index 00000000..53bc6cb5 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.kt @@ -0,0 +1,65 @@ +package code.name.monkey.retromusic.dialogs + +import android.content.res.ColorStateList +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import butterknife.ButterKnife +import code.name.monkey.appthemehelper.ThemeStore +import code.name.monkey.appthemehelper.util.MaterialUtil +import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.util.PlaylistsUtil +import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment +import kotlinx.android.synthetic.main.dialog_playlist.* + +class RenamePlaylistDialog : RoundedBottomSheetDialogFragment() { + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + val layout = inflater.inflate(R.layout.dialog_playlist, container, false) + ButterKnife.bind(this, layout) + return layout + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val accentColor = ThemeStore.accentColor(context!!) + actionCreate.setText(R.string.action_rename) + + MaterialUtil.setTint(actionCreate, true) + MaterialUtil.setTint(actionCancel, false) + MaterialUtil.setTint(actionNewPlaylistContainer, false) + + actionNewPlaylist.apply { + var playlistId: Long = 0 + if (arguments != null) { + playlistId = arguments!!.getLong("playlist_id") + } + setText(PlaylistsUtil.getNameForPlaylist(activity!!, playlistId)) + setHintTextColor(ColorStateList.valueOf(accentColor)) + setTextColor(ThemeStore.textColorPrimary(context!!)) + } + + bannerTitle.setTextColor(ThemeStore.textColorPrimary(context!!)) + bannerTitle.setText(R.string.rename_playlist_title) + actionCancel.setOnClickListener { dismiss() } + actionCreate.setOnClickListener { + if (actionNewPlaylist.toString().trim { it <= ' ' } != "") { + val playlistId = arguments!!.getLong("playlist_id") + PlaylistsUtil.renamePlaylist(context!!, playlistId, actionNewPlaylist.text!!.toString()) + } + } + } + + companion object { + + fun create(playlistId: Long): RenamePlaylistDialog { + val dialog = RenamePlaylistDialog() + val args = Bundle() + args.putLong("playlist_id", playlistId) + dialog.arguments = args + return dialog + } + } +} \ No newline at end of file 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 index 7bf2c1e9..2a779a2a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/ArtistGlideRequest.java +++ b/app/src/main/java/code/name/monkey/retromusic/glide/ArtistGlideRequest.java @@ -15,7 +15,7 @@ import com.bumptech.glide.load.resource.drawable.GlideDrawable; import com.bumptech.glide.request.target.Target; import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.RetroApplication; +import code.name.monkey.retromusic.App; import code.name.monkey.retromusic.glide.artistimage.ArtistImage; import code.name.monkey.retromusic.glide.palette.BitmapPaletteTranscoder; import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper; @@ -31,7 +31,7 @@ public class ArtistGlideRequest { private static DrawableTypeRequest createBaseRequest(RequestManager requestManager, Artist artist, boolean noCustomImage, boolean forceDownload) { - boolean hasCustomImage = CustomArtistImageUtil.getInstance(RetroApplication.Companion.getInstance()) + boolean hasCustomImage = CustomArtistImageUtil.getInstance(App.Companion.getInstance()) .hasCustomArtistImage(artist); if (noCustomImage || !hasCustomImage) { return requestManager.load(new ArtistImage(artist.getName(), forceDownload)); @@ -41,7 +41,7 @@ public class ArtistGlideRequest { } private static Key createSignature(Artist artist) { - return ArtistSignatureUtil.getInstance(RetroApplication.Companion.getInstance()) + return ArtistSignatureUtil.getInstance(App.Companion.getInstance()) .getArtistSignature(artist.getName()); } 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 281dc96a..5defa713 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 @@ -7,7 +7,6 @@ import com.bumptech.glide.request.animation.GlideAnimation import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.retromusic.R -import code.name.monkey.retromusic.RetroApplication import code.name.monkey.retromusic.glide.palette.BitmapPaletteTarget import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper import code.name.monkey.retromusic.util.PreferenceUtil 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 cf685bb2..cec5bd45 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 @@ -12,7 +12,7 @@ import android.provider.MediaStore import android.util.Log import android.widget.Toast import code.name.monkey.retromusic.R -import code.name.monkey.retromusic.RetroApplication +import code.name.monkey.retromusic.App import code.name.monkey.retromusic.loaders.SongLoader import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.service.MusicService @@ -33,7 +33,7 @@ object MusicPlayerRemote { private val castSession: CastSession? get() { - val castSession = CastContext.getSharedInstance(RetroApplication.instance).sessionManager.currentCastSession + val castSession = CastContext.getSharedInstance(App.instance).sessionManager.currentCastSession if (castSession != null) { playbackLocation = PlaybackLocation.REMOTE } else { @@ -48,7 +48,7 @@ object MusicPlayerRemote { val currentSong: Song get() = if (musicService != null) { musicService!!.currentSong - } else Song.EMPTY_SONG + } else Song.emptySong /** * Async diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/menu/GenreMenuHelper.kt b/app/src/main/java/code/name/monkey/retromusic/helper/menu/GenreMenuHelper.kt index 6796361f..f9c127ce 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/menu/GenreMenuHelper.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/menu/GenreMenuHelper.kt @@ -1,26 +1,22 @@ package code.name.monkey.retromusic.helper.menu import android.app.Activity -import androidx.appcompat.app.AppCompatActivity import android.view.MenuItem - -import java.util.ArrayList - -import code.name.monkey.retromusic.loaders.GenreLoader -import code.name.monkey.retromusic.model.Genre -import code.name.monkey.retromusic.model.Song +import androidx.appcompat.app.AppCompatActivity import code.name.monkey.retromusic.R import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog import code.name.monkey.retromusic.helper.MusicPlayerRemote +import code.name.monkey.retromusic.loaders.GenreLoader +import code.name.monkey.retromusic.model.Genre +import code.name.monkey.retromusic.model.Song +import java.util.* /** * @author Hemanth S (h4h13). */ object GenreMenuHelper { - fun handleMenuClick(activity: AppCompatActivity, - genre: Genre, - item: MenuItem): Boolean { + fun handleMenuClick(activity: AppCompatActivity, genre: Genre, item: MenuItem): Boolean { when (item.itemId) { R.id.action_play -> { MusicPlayerRemote.openQueue(getGenreSongs(activity, genre), 0, true) diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/menu/PlaylistMenuHelper.kt b/app/src/main/java/code/name/monkey/retromusic/helper/menu/PlaylistMenuHelper.kt index 5289ad31..44481a36 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/menu/PlaylistMenuHelper.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/menu/PlaylistMenuHelper.kt @@ -9,7 +9,7 @@ import android.widget.Toast import java.util.ArrayList import code.name.monkey.retromusic.R -import code.name.monkey.retromusic.RetroApplication +import code.name.monkey.retromusic.App import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog import code.name.monkey.retromusic.dialogs.DeletePlaylistDialog import code.name.monkey.retromusic.dialogs.RenamePlaylistDialog @@ -76,8 +76,8 @@ object PlaylistMenuHelper { private class SavePlaylistAsyncTask internal constructor(context: Context) : WeakContextAsyncTask(context) { override fun doInBackground(vararg params: Playlist): String { - return String.format(RetroApplication.instance.applicationContext.getString(R.string - .saved_playlist_to), PlaylistsUtil.savePlaylist(RetroApplication.instance.applicationContext, params[0]).blockingFirst()) + return String.format(App.instance.applicationContext.getString(R.string + .saved_playlist_to), PlaylistsUtil.savePlaylist(App.instance.applicationContext, params[0]).blockingFirst()) } override fun onPostExecute(string: String) { diff --git a/app/src/main/java/code/name/monkey/retromusic/loaders/SongLoader.kt b/app/src/main/java/code/name/monkey/retromusic/loaders/SongLoader.kt index c6e7f40c..9359e339 100644 --- a/app/src/main/java/code/name/monkey/retromusic/loaders/SongLoader.kt +++ b/app/src/main/java/code/name/monkey/retromusic/loaders/SongLoader.kt @@ -119,7 +119,7 @@ object SongLoader { val song: Song = if (cursor != null && cursor.moveToFirst()) { getSongFromCursorImpl(cursor) } else { - Song.EMPTY_SONG + Song.emptySong } cursor?.close() e.onNext(song) 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 27405c44..dc9df05c 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 @@ -36,6 +36,6 @@ class Album { } fun safeGetFirstSong(): Song { - return if (songs!!.isEmpty()) Song.EMPTY_SONG else songs[0] + return if (songs!!.isEmpty()) Song.emptySong else songs[0] } } diff --git a/app/src/main/java/code/name/monkey/retromusic/model/Genre.java b/app/src/main/java/code/name/monkey/retromusic/model/Genre.java new file mode 100644 index 00000000..9522536d --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/model/Genre.java @@ -0,0 +1,86 @@ +package code.name.monkey.retromusic.model; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * @author Hemanth S (h4h13). + */ + +public class Genre implements Parcelable { + + public static final Creator CREATOR = new Creator() { + @Override + public Genre createFromParcel(Parcel in) { + return new Genre(in); + } + + @Override + public Genre[] newArray(int size) { + return new Genre[size]; + } + }; + public final int id; + public final String name; + public final int songCount; + + public Genre(final int id, final String name, int songCount) { + this.id = id; + this.name = name; + this.songCount = songCount; + } + + + // For unknown genre + public Genre(final String name, final int songCount) { + this.id = -1; + this.name = name; + this.songCount = songCount; + } + + protected Genre(Parcel in) { + id = in.readInt(); + name = in.readString(); + songCount = in.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(id); + dest.writeString(name); + dest.writeInt(songCount); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Genre genre = (Genre) o; + + if (id != genre.id) return false; + return name != null ? name.equals(genre.name) : genre.name == null; + } + + @Override + public int hashCode() { + int result = id; + result = 31 * result + (name != null ? name.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "Genre{" + + "id=" + id + + ", name='" + name + '\'' + + '}'; + } + + +} \ No newline at end of file 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 deleted file mode 100644 index 9448e18e..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/model/Genre.kt +++ /dev/null @@ -1,26 +0,0 @@ -package code.name.monkey.retromusic.model - -import java.io.Serializable - -/** - * @author Hemanth S (h4h13). - */ - -class Genre : Serializable { - val id: Int - val name: String? - val songCount: Int - - constructor(id: Int, name: String, songCount: Int) { - this.id = id - this.name = name - this.songCount = songCount - } - - // For unknown genre - constructor(name: String, songCount: Int) { - this.id = -1 - this.name = name - this.songCount = songCount - } -} \ No newline at end of file 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 fab260e2..a395b699 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 @@ -64,7 +64,9 @@ open class Song : Parcelable { } companion object { - val EMPTY_SONG = Song(-1, "", -1, -1, -1, "", -1, -1, "", -1, "") + + var emptySong = Song(-1, "", -1, -1, -1, "", -1, -1, "", -1, "") + @JvmField val CREATOR: Parcelable.Creator = object : Parcelable.Creator { override fun createFromParcel(source: Parcel): Song { diff --git a/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/ArtistDetailsPresenter.java b/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/ArtistDetailsPresenter.java index 0885f68a..c09e12fc 100644 --- a/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/ArtistDetailsPresenter.java +++ b/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/ArtistDetailsPresenter.java @@ -1,13 +1,12 @@ package code.name.monkey.retromusic.mvp.presenter; import android.os.Bundle; -import androidx.annotation.NonNull; +import androidx.annotation.NonNull; import code.name.monkey.retromusic.model.Artist; import code.name.monkey.retromusic.mvp.Presenter; import code.name.monkey.retromusic.mvp.contract.ArtistDetailContract; - -import static code.name.monkey.retromusic.ui.activities.ArtistDetailActivity.EXTRA_ARTIST_ID; +import code.name.monkey.retromusic.ui.activities.ArtistDetailActivity; /** @@ -38,7 +37,7 @@ public class ArtistDetailsPresenter extends Presenter implements ArtistDetailCon @Override public void loadArtistById() { - disposable.add(repository.getArtistById(bundle.getInt(EXTRA_ARTIST_ID)) + disposable.add(repository.getArtistById(bundle.getInt(ArtistDetailActivity.EXTRA_ARTIST_ID)) .subscribeOn(schedulerProvider.computation()) .observeOn(schedulerProvider.ui()) .doOnSubscribe(disposable1 -> view.loading()) diff --git a/app/src/main/java/code/name/monkey/retromusic/preferences/NowPlayingScreenPreferenceDialog.java b/app/src/main/java/code/name/monkey/retromusic/preferences/NowPlayingScreenPreferenceDialog.java index 6c68c8ca..59d7fde5 100644 --- a/app/src/main/java/code/name/monkey/retromusic/preferences/NowPlayingScreenPreferenceDialog.java +++ b/app/src/main/java/code/name/monkey/retromusic/preferences/NowPlayingScreenPreferenceDialog.java @@ -21,7 +21,7 @@ import androidx.fragment.app.DialogFragment; import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager.widget.ViewPager; import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.RetroApplication; +import code.name.monkey.retromusic.App; import code.name.monkey.retromusic.ui.fragments.NowPlayingScreen; import code.name.monkey.retromusic.util.NavigationUtil; import code.name.monkey.retromusic.util.PreferenceUtil; @@ -97,8 +97,8 @@ public class NowPlayingScreenPreferenceDialog extends DialogFragment implements nowPlayingScreen.equals(NowPlayingScreen.TINY) || nowPlayingScreen.equals(NowPlayingScreen.BLUR_CARD)|| nowPlayingScreen.equals(NowPlayingScreen.ADAPTIVE)) - && !RetroApplication.Companion.isProVersion();*/ - return !RetroApplication.Companion.isProVersion(); + && !App.Companion.isProVersion();*/ + return !App.Companion.isProVersion(); } @Override diff --git a/app/src/main/java/code/name/monkey/retromusic/providers/RepositoryImpl.java b/app/src/main/java/code/name/monkey/retromusic/providers/RepositoryImpl.java index 7deda91e..d65c7b6b 100644 --- a/app/src/main/java/code/name/monkey/retromusic/providers/RepositoryImpl.java +++ b/app/src/main/java/code/name/monkey/retromusic/providers/RepositoryImpl.java @@ -6,7 +6,7 @@ import java.io.File; import java.util.ArrayList; import code.name.monkey.retromusic.Injection; -import code.name.monkey.retromusic.RetroApplication; +import code.name.monkey.retromusic.App; import code.name.monkey.retromusic.loaders.AlbumLoader; import code.name.monkey.retromusic.loaders.ArtistLoader; import code.name.monkey.retromusic.loaders.GenreLoader; @@ -41,7 +41,7 @@ public class RepositoryImpl implements Repository { public static synchronized RepositoryImpl getInstance() { if (INSTANCE == null) { - INSTANCE = new RepositoryImpl(RetroApplication.Companion.getInstance()); + INSTANCE = new RepositoryImpl(App.Companion.getInstance()); } return INSTANCE; } diff --git a/app/src/main/java/code/name/monkey/retromusic/rest/KogouClient.java b/app/src/main/java/code/name/monkey/retromusic/rest/KogouClient.java index fcf0c7cd..dca902fa 100644 --- a/app/src/main/java/code/name/monkey/retromusic/rest/KogouClient.java +++ b/app/src/main/java/code/name/monkey/retromusic/rest/KogouClient.java @@ -6,7 +6,7 @@ import java.io.File; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import code.name.monkey.retromusic.RetroApplication; +import code.name.monkey.retromusic.App; import code.name.monkey.retromusic.rest.service.KuGouApiService; import okhttp3.Cache; import okhttp3.Call; @@ -69,7 +69,7 @@ public class KogouClient { return new OkHttpClient.Builder() .addInterceptor(interceptor) - .cache(createDefaultCache(RetroApplication.Companion.getInstance())) + .cache(createDefaultCache(App.Companion.getInstance())) .addInterceptor(createCacheControlInterceptor()); } 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 new file mode 100644 index 00000000..d725f40b --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/service/MultiPlayer.java @@ -0,0 +1,334 @@ +package code.name.monkey.retromusic.service; + +import android.content.Context; +import android.content.Intent; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.audiofx.AudioEffect; +import android.net.Uri; +import android.os.PowerManager; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import android.util.Log; +import android.widget.Toast; + +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(); + + private MediaPlayer mCurrentMediaPlayer = new MediaPlayer(); + private MediaPlayer mNextMediaPlayer; + + private Context context; + @Nullable + private Playback.PlaybackCallbacks callbacks; + + private boolean mIsInitialized = false; + + /** + * 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); + } + 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.getInstance().gaplessPlayback()) { + 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(); + } + } + + /** + * Pauses playback. Call start() to resume. + */ + @Override + public boolean pause() { + try { + mCurrentMediaPlayer.pause(); + return true; + } catch (IllegalStateException e) { + return false; + } + } + + /** + * Checks whether the MultiPlayer is playing. + */ + @Override + public boolean isPlaying() { + return mIsInitialized && mCurrentMediaPlayer.isPlaying(); + } + + /** + * 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; + } + } + + /** + * 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 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; + } + } + + @Override + public boolean setVolume(final float vol) { + try { + mCurrentMediaPlayer.setVolume(vol, vol); + return true; + } catch (IllegalStateException e) { + return false; + } + } + + /** + * 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; + } + } + + /** + * 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(); + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public void onCompletion(final MediaPlayer mp) { + if (mp == 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/MultiPlayer.kt b/app/src/main/java/code/name/monkey/retromusic/service/MultiPlayer.kt deleted file mode 100644 index ba24b958..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/service/MultiPlayer.kt +++ /dev/null @@ -1,333 +0,0 @@ -package code.name.monkey.retromusic.service - -import android.content.Context -import android.content.Intent -import android.media.AudioManager -import android.media.MediaPlayer -import android.media.audiofx.AudioEffect -import android.net.Uri -import android.os.PowerManager -import android.util.Log -import android.widget.Toast - -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) - */ -class MultiPlayer -/** - * Constructor of `MultiPlayer` - */ -internal constructor(private val context: Context?) : Playback, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener { - - private var mCurrentMediaPlayer = MediaPlayer() - private var mNextMediaPlayer: MediaPlayer? = null - private var callbacks: Playback.PlaybackCallbacks? = null - - private var mIsInitialized = false - - init { - 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 fun setDataSource(path: String): Boolean { - mIsInitialized = false - mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path) - if (mIsInitialized) { - setNextDataSource(null) - } - return mIsInitialized - } - - /** - * @param player The [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 { - 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 (e: Exception) { - 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 - } - - /** - * 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 fun setNextDataSource(path: String?) { - if (context == null) { - return - } - try { - mCurrentMediaPlayer.setNextMediaPlayer(null) - } catch (e: IllegalArgumentException) { - Log.i(TAG, "Next media player is current one, continuing") - } catch (e: IllegalStateException) { - Log.e(TAG, "Media player not initialized!") - return - } - - if (mNextMediaPlayer != null) { - mNextMediaPlayer!!.release() - mNextMediaPlayer = null - } - if (path == null) { - return - } - if (PreferenceUtil.getInstance().gaplessPlayback()) { - mNextMediaPlayer = MediaPlayer() - mNextMediaPlayer!!.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK) - mNextMediaPlayer!!.audioSessionId = audioSessionId - if (setDataSourceImpl(mNextMediaPlayer!!, path)) { - try { - mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer) - } catch (e: IllegalArgumentException) { - Log.e(TAG, "setNextDataSource: setNextMediaPlayer()", e) - if (mNextMediaPlayer != null) { - mNextMediaPlayer!!.release() - mNextMediaPlayer = null - } - } catch (e: IllegalStateException) { - 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 fun setCallbacks(callbacks: Playback.PlaybackCallbacks?) { - this.callbacks = callbacks - } - - /** - * @return True if the player is ready to go, false otherwise - */ - override fun isInitialized(): Boolean { - return mIsInitialized - } - - /** - * Starts or resumes playback. - */ - override fun start(): Boolean { - try { - mCurrentMediaPlayer.start() - return true - } catch (e: IllegalStateException) { - return false - } - - } - - /** - * Resets the MediaPlayer to its uninitialized state. - */ - override fun stop() { - mCurrentMediaPlayer.reset() - mIsInitialized = false - } - - /** - * Releases resources associated with this MediaPlayer object. - */ - override fun release() { - stop() - mCurrentMediaPlayer.release() - if (mNextMediaPlayer != null) { - mNextMediaPlayer!!.release() - } - } - - /** - * Pauses playback. Call start() to resume. - */ - override fun pause(): Boolean { - try { - mCurrentMediaPlayer.pause() - return true - } catch (e: IllegalStateException) { - return false - } - - } - - /** - * Checks whether the MultiPlayer is playing. - */ - override fun isPlaying(): Boolean { - return mIsInitialized && mCurrentMediaPlayer.isPlaying - } - - /** - * Gets the duration of the file. - * - * @return The duration in milliseconds - */ - override fun duration(): Int { - if (!mIsInitialized) { - return -1 - } - try { - return mCurrentMediaPlayer.duration - } catch (e: IllegalStateException) { - return -1 - } - - } - - /** - * Gets the current playback position. - * - * @return The current position in milliseconds - */ - override fun position(): Int { - if (!mIsInitialized) { - return -1 - } - try { - return mCurrentMediaPlayer.currentPosition - } catch (e: IllegalStateException) { - 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 fun seek(whereto: Int): Int { - try { - mCurrentMediaPlayer.seekTo(whereto) - return whereto - } catch (e: IllegalStateException) { - return -1 - } - - } - - override fun setVolume(vol: Float): Boolean { - try { - mCurrentMediaPlayer.setVolume(vol, vol) - return true - } catch (e: IllegalStateException) { - return false - } - - } - - /** - * Sets the audio session ID. - * - * @param sessionId The audio session ID - */ - override fun setAudioSessionId(sessionId: Int): Boolean { - try { - mCurrentMediaPlayer.audioSessionId = sessionId - return true - } catch (e: IllegalArgumentException) { - return false - } catch (e: IllegalStateException) { - return false - } - - } - - /** - * Returns the audio session ID. - * - * @return The current audio session ID. - */ - override fun getAudioSessionId(): Int { - return mCurrentMediaPlayer.audioSessionId - } - - /** - * {@inheritDoc} - */ - override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean { - mIsInitialized = false - mCurrentMediaPlayer.release() - mCurrentMediaPlayer = MediaPlayer() - mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK) - if (context != null) { - Toast.makeText(context, context.resources.getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show() - } - return false - } - - /** - * {@inheritDoc} - */ - override fun onCompletion(mp: MediaPlayer) { - if (mp === mCurrentMediaPlayer && mNextMediaPlayer != null) { - mIsInitialized = false - mCurrentMediaPlayer.release() - mCurrentMediaPlayer = mNextMediaPlayer as MediaPlayer - mIsInitialized = true - mNextMediaPlayer = null - if (callbacks != null) - callbacks!!.onTrackWentToNext() - } else { - if (callbacks != null) - callbacks!!.onTrackEnded() - } - } - - companion object { - val TAG: String = MultiPlayer::class.java.simpleName - } - - -} \ 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 new file mode 100644 index 00000000..39d4e0f0 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java @@ -0,0 +1,1420 @@ +package code.name.monkey.retromusic.service; + +import android.app.PendingIntent; +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.database.ContentObserver; +import android.graphics.Bitmap; +import android.graphics.Point; +import android.graphics.drawable.Drawable; +import android.media.AudioManager; +import android.media.audiofx.AudioEffect; +import android.media.session.MediaSession; +import android.os.Binder; +import android.os.Build; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.Process; +import android.preference.PreferenceManager; +import android.provider.MediaStore; +import android.support.v4.media.MediaMetadataCompat; +import android.support.v4.media.session.MediaSessionCompat; +import android.support.v4.media.session.PlaybackStateCompat; +import android.telephony.PhoneStateListener; +import android.telephony.TelephonyManager; +import android.util.Log; +import android.widget.Toast; + +import com.bumptech.glide.BitmapRequestBuilder; +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.SimpleTarget; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.glide.BlurTransformation; +import code.name.monkey.retromusic.glide.SongGlideRequest; +import code.name.monkey.retromusic.helper.ShuffleHelper; +import code.name.monkey.retromusic.helper.StopWatch; +import code.name.monkey.retromusic.loaders.PlaylistSongsLoader; +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.providers.HistoryStore; +import code.name.monkey.retromusic.providers.MusicPlaybackQueueStore; +import code.name.monkey.retromusic.providers.SongPlayCountStore; +import code.name.monkey.retromusic.service.notification.PlayingNotification; +import code.name.monkey.retromusic.service.notification.PlayingNotificationImpl24; +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.PreferenceUtil; +import code.name.monkey.retromusic.util.RetroUtil; + +import static code.name.monkey.retromusic.Constants.ACTION_PAUSE; +import static code.name.monkey.retromusic.Constants.ACTION_PLAY; +import static code.name.monkey.retromusic.Constants.ACTION_PLAY_PLAYLIST; +import static code.name.monkey.retromusic.Constants.ACTION_QUIT; +import static code.name.monkey.retromusic.Constants.ACTION_REWIND; +import static code.name.monkey.retromusic.Constants.ACTION_SKIP; +import static code.name.monkey.retromusic.Constants.ACTION_STOP; +import static code.name.monkey.retromusic.Constants.ACTION_TOGGLE_PAUSE; +import static code.name.monkey.retromusic.Constants.APP_WIDGET_UPDATE; +import static code.name.monkey.retromusic.Constants.EXTRA_APP_WIDGET_NAME; +import static code.name.monkey.retromusic.Constants.INTENT_EXTRA_PLAYLIST; +import static code.name.monkey.retromusic.Constants.INTENT_EXTRA_SHUFFLE_MODE; +import static code.name.monkey.retromusic.Constants.MEDIA_STORE_CHANGED; +import static code.name.monkey.retromusic.Constants.META_CHANGED; +import static code.name.monkey.retromusic.Constants.MUSIC_PACKAGE_NAME; +import static code.name.monkey.retromusic.Constants.PLAY_STATE_CHANGED; +import static code.name.monkey.retromusic.Constants.QUEUE_CHANGED; +import static code.name.monkey.retromusic.Constants.REPEAT_MODE_CHANGED; +import static code.name.monkey.retromusic.Constants.RETRO_MUSIC_PACKAGE_NAME; +import static code.name.monkey.retromusic.Constants.SHUFFLE_MODE_CHANGED; + +/** + * @author Karim Abou Zeid (kabouzeid), Andrew Neal + */ +public class MusicService extends Service implements SharedPreferences.OnSharedPreferenceChangeListener, Playback.PlaybackCallbacks { + public static final String TAG = MusicService.class.getSimpleName(); + + public static final String SAVED_POSITION = "POSITION"; + public static final String SAVED_POSITION_IN_TRACK = "POSITION_IN_TRACK"; + public static final String SAVED_SHUFFLE_MODE = "SHUFFLE_MODE"; + public static final String SAVED_REPEAT_MODE = "REPEAT_MODE"; + + public static final int RELEASE_WAKELOCK = 0; + public static final int TRACK_ENDED = 1; + public static final int TRACK_WENT_TO_NEXT = 2; + public static final int PLAY_SONG = 3; + public static final int PREPARE_NEXT = 4; + public static final int SET_POSITION = 5; + public static final int RESTORE_QUEUES = 9; + public static final int SHUFFLE_MODE_NONE = 0; + public static final int SHUFFLE_MODE_SHUFFLE = 1; + public static final int REPEAT_MODE_NONE = 0; + public static final int REPEAT_MODE_ALL = 1; + public static final int REPEAT_MODE_THIS = 2; + public static final int SAVE_QUEUES = 0; + private static final int FOCUS_CHANGE = 6; + private static final int DUCK = 7; + private static final int UNDUCK = 8; + private static final long MEDIA_SESSION_ACTIONS = PlaybackStateCompat.ACTION_PLAY + | PlaybackStateCompat.ACTION_PAUSE + | PlaybackStateCompat.ACTION_PLAY_PAUSE + | PlaybackStateCompat.ACTION_SKIP_TO_NEXT + | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS + | PlaybackStateCompat.ACTION_STOP + | PlaybackStateCompat.ACTION_SEEK_TO; + private final IBinder musicBind = new MusicBinder(); + private final BroadcastReceiver widgetIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(final Context context, final Intent intent) { + final String command = intent.getStringExtra(EXTRA_APP_WIDGET_NAME); + + } + }; + private Playback playback; + private ArrayList playingQueue = new ArrayList<>(); + private ArrayList originalPlayingQueue = new ArrayList<>(); + private int position = -1; + private int nextPosition = -1; + private int shuffleMode; + private int repeatMode; + private boolean queuesRestored; + private boolean pausedByTransientLossOfFocus; + private final BroadcastReceiver becomingNoisyReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, @NonNull Intent intent) { + if (intent.getAction() != null && intent.getAction().equals(AudioManager.ACTION_AUDIO_BECOMING_NOISY)) { + pause(); + } + } + }; + private PlayingNotification playingNotification; + private AudioManager audioManager; + @SuppressWarnings("deprecation") + private MediaSessionCompat mediaSession; + private PowerManager.WakeLock wakeLock; + private PlaybackHandler playerHandler; + private final AudioManager.OnAudioFocusChangeListener audioFocusListener = new AudioManager.OnAudioFocusChangeListener() { + @Override + public void onAudioFocusChange(final int focusChange) { + playerHandler.obtainMessage(FOCUS_CHANGE, focusChange, 0).sendToTarget(); + } + }; + private QueueSaveHandler queueSaveHandler; + private HandlerThread musicPlayerHandlerThread; + private HandlerThread queueSaveHandlerThread; + private SongPlayCountHelper songPlayCountHelper = new SongPlayCountHelper(); + private ThrottledSeekHandler throttledSeekHandler; + private boolean becomingNoisyReceiverRegistered; + private IntentFilter becomingNoisyReceiverIntentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY); + private ContentObserver mediaStoreObserver; + private boolean notHandledMetaChangedForCurrentTrack; + private PhoneStateListener phoneStateListener = new PhoneStateListener() { + @Override + public void onCallStateChanged(int state, String incomingNumber) { + switch (state) { + case TelephonyManager.CALL_STATE_IDLE: + //Not in call: Play music + play(); + break; + case TelephonyManager.CALL_STATE_RINGING: + case TelephonyManager.CALL_STATE_OFFHOOK: + //A call is dialing, active or on hold + pause(); + break; + default: + } + super.onCallStateChanged(state, incomingNumber); + } + }; + private boolean isServiceBound; + private Handler uiThreadHandler; + private IntentFilter headsetReceiverIntentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); + private boolean headsetReceiverRegistered = false; + private BroadcastReceiver headsetReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action != null) { + switch (action) { + case Intent.ACTION_HEADSET_PLUG: + int state = intent.getIntExtra("state", -1); + switch (state) { + case 0: + Log.d(TAG, "Headset unplugged"); + pause(); + break; + case 1: + Log.d(TAG, "Headset plugged"); + play(); + break; + } + break; + } + } + } + }; + + private static String getTrackUri(@NonNull Song song) { + return MusicUtil.getSongFileUri(song.getId()).toString(); + } + + private static Bitmap copy(Bitmap bitmap) { + Bitmap.Config config = bitmap.getConfig(); + if (config == null) { + config = Bitmap.Config.RGB_565; + } + try { + return bitmap.copy(config, false); + } catch (OutOfMemoryError e) { + e.printStackTrace(); + return null; + } + } + + + @Override + public void onCreate() { + super.onCreate(); + + final TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); + if (telephonyManager != null) { + telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE); + } + + final PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); + if (powerManager != null) { + wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName()); + } + wakeLock.setReferenceCounted(false); + + musicPlayerHandlerThread = new HandlerThread("PlaybackHandler"); + musicPlayerHandlerThread.start(); + playerHandler = new PlaybackHandler(this, musicPlayerHandlerThread.getLooper()); + + playback = new MultiPlayer(this); + playback.setCallbacks(this); + + setupMediaSession(); + + // queue saving needs to run on a separate thread so that it doesn't block the playback handler events + queueSaveHandlerThread = new HandlerThread("QueueSaveHandler", Process.THREAD_PRIORITY_BACKGROUND); + queueSaveHandlerThread.start(); + queueSaveHandler = new QueueSaveHandler(this, queueSaveHandlerThread.getLooper()); + + uiThreadHandler = new Handler(); + + registerReceiver(widgetIntentReceiver, new IntentFilter(APP_WIDGET_UPDATE)); + + initNotification(); + + mediaStoreObserver = new MediaStoreObserver(playerHandler); + throttledSeekHandler = new ThrottledSeekHandler(playerHandler); + getContentResolver().registerContentObserver( + MediaStore.Audio.Media.INTERNAL_CONTENT_URI, true, mediaStoreObserver); + getContentResolver().registerContentObserver( + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); + + PreferenceUtil.getInstance().registerOnSharedPreferenceChangedListener(this); + + restoreState(); + + mediaSession.setActive(true); + + sendBroadcast(new Intent("code.name.monkey.retromusic.RETRO_MUSIC_SERVICE_CREATED")); + + registerHeadsetEvents(); + + + } + + private AudioManager getAudioManager() { + if (audioManager == null) { + audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + } + return audioManager; + } + + private void setupMediaSession() { + ComponentName mediaButtonReceiverComponentName = new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class); + + Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); + mediaButtonIntent.setComponent(mediaButtonReceiverComponentName); + + + PendingIntent mediaButtonReceiverPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0); + + mediaSession = new MediaSessionCompat(this, "RetroMusicPlayer", mediaButtonReceiverComponentName, mediaButtonReceiverPendingIntent); + mediaSession.setCallback(new MediaSessionCompat.Callback() { + @Override + public void onPlay() { + play(); + } + + @Override + public void onPause() { + pause(); + } + + @Override + public void onSkipToNext() { + playNextSong(true); + } + + @Override + public void onSkipToPrevious() { + back(true); + } + + @Override + public void onStop() { + quit(); + } + + @Override + public void onSeekTo(long pos) { + seek((int) pos); + } + + @Override + public boolean onMediaButtonEvent(Intent mediaButtonEvent) { + return MediaButtonIntentReceiver.Companion.handleIntent(MusicService.this, mediaButtonEvent); + } + }); + + mediaSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS + | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS); + + mediaSession.setMediaButtonReceiver(mediaButtonReceiverPendingIntent); + } + + @Override + public int onStartCommand(@Nullable Intent intent, int flags, int startId) { + if (intent != null) { + if (intent.getAction() != null) { + restoreQueuesAndPositionIfNecessary(); + String action = intent.getAction(); + switch (action) { + case ACTION_TOGGLE_PAUSE: + if (isPlaying()) { + pause(); + } else { + play(); + } + break; + case ACTION_PAUSE: + pause(); + break; + case ACTION_PLAY: + play(); + break; + case ACTION_PLAY_PLAYLIST: + Playlist playlist = intent.getParcelableExtra(INTENT_EXTRA_PLAYLIST); + int shuffleMode = intent.getIntExtra(INTENT_EXTRA_SHUFFLE_MODE, getShuffleMode()); + if (playlist != null) { + ArrayList playlistSongs; + if (playlist instanceof AbsCustomPlaylist) { + playlistSongs = ((AbsCustomPlaylist) playlist).getSongs(getApplicationContext()).blockingFirst(); + } else { + //noinspection unchecked + playlistSongs = PlaylistSongsLoader.INSTANCE.getPlaylistSongList(getApplicationContext(), playlist.id).blockingFirst(); + } + if (!playlistSongs.isEmpty()) { + if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { + int startPosition; + startPosition = new Random().nextInt(playlistSongs.size()); + openQueue(playlistSongs, startPosition, true); + setShuffleMode(shuffleMode); + } else { + openQueue(playlistSongs, 0, true); + } + } else { + Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG).show(); + } + } else { + Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG).show(); + } + break; + case ACTION_REWIND: + back(true); + break; + case ACTION_SKIP: + playNextSong(true); + break; + case ACTION_STOP: + case ACTION_QUIT: + return quit(); + } + } + } + + return START_STICKY; + } + + + @Override + public void onDestroy() { + unregisterReceiver(widgetIntentReceiver); + if (becomingNoisyReceiverRegistered) { + unregisterReceiver(becomingNoisyReceiver); + becomingNoisyReceiverRegistered = false; + } + if (headsetReceiverRegistered) { + unregisterReceiver(headsetReceiver); + headsetReceiverRegistered = false; + } + mediaSession.setActive(false); + quit(); + releaseResources(); + getContentResolver().unregisterContentObserver(mediaStoreObserver); + PreferenceUtil.getInstance().unregisterOnSharedPreferenceChangedListener(this); + wakeLock.release(); + + sendBroadcast(new Intent("code.name.monkey.retromusic.RETRO_MUSIC_MUSIC_SERVICE_DESTROYED")); + } + + @Override + public IBinder onBind(Intent intent) { + isServiceBound = true; + return musicBind; + } + + @Override + public void onRebind(Intent intent) { + isServiceBound = true; + } + + @Override + public boolean onUnbind(Intent intent) { + isServiceBound = false; + if (!isPlaying()) { + stopSelf(); + } + return true; + } + + private void saveQueuesImpl() { + MusicPlaybackQueueStore.getInstance(this).saveQueues(playingQueue, originalPlayingQueue); + } + + private void savePosition() { + PreferenceManager.getDefaultSharedPreferences(this).edit().putInt(SAVED_POSITION, getPosition()).apply(); + } + + private void savePositionInTrack() { + PreferenceManager.getDefaultSharedPreferences(this).edit().putInt(SAVED_POSITION_IN_TRACK, getSongProgressMillis()).apply(); + } + + public void saveState() { + saveQueues(); + savePosition(); + savePositionInTrack(); + } + + private void saveQueues() { + queueSaveHandler.removeMessages(SAVE_QUEUES); + queueSaveHandler.sendEmptyMessage(SAVE_QUEUES); + } + + private void restoreState() { + shuffleMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_SHUFFLE_MODE, 0); + repeatMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_REPEAT_MODE, 0); + handleAndSendChangeInternal(SHUFFLE_MODE_CHANGED); + handleAndSendChangeInternal(REPEAT_MODE_CHANGED); + + playerHandler.removeMessages(RESTORE_QUEUES); + playerHandler.sendEmptyMessage(RESTORE_QUEUES); + } + + private synchronized void restoreQueuesAndPositionIfNecessary() { + if (!queuesRestored && playingQueue.isEmpty()) { + ArrayList restoredQueue = MusicPlaybackQueueStore.getInstance(this).getSavedPlayingQueue() + .blockingFirst(); + + ArrayList restoredOriginalQueue = MusicPlaybackQueueStore.getInstance(this).getSavedOriginalPlayingQueue() + .blockingFirst(); + + int restoredPosition = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_POSITION, -1); + int restoredPositionInTrack = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_POSITION_IN_TRACK, -1); + + if (restoredQueue.size() > 0 && restoredQueue.size() == restoredOriginalQueue.size() && restoredPosition != -1) { + this.originalPlayingQueue = restoredOriginalQueue; + this.playingQueue = restoredQueue; + + position = restoredPosition; + openCurrent(); + prepareNext(); + + if (restoredPositionInTrack > 0) seek(restoredPositionInTrack); + + notHandledMetaChangedForCurrentTrack = true; + sendChangeInternal(META_CHANGED); + sendChangeInternal(QUEUE_CHANGED); + } + } + queuesRestored = true; + } + + private int quit() { + pause(); + playingNotification.stop(); + + if (isServiceBound) { + return START_STICKY; + } else { + closeAudioEffectSession(); + getAudioManager().abandonAudioFocus(audioFocusListener); + stopSelf(); + return START_NOT_STICKY; + } + } + + private void releaseResources() { + playerHandler.removeCallbacksAndMessages(null); + musicPlayerHandlerThread.quitSafely(); + queueSaveHandler.removeCallbacksAndMessages(null); + queueSaveHandlerThread.quitSafely(); + playback.release(); + playback = null; + mediaSession.release(); + } + + public boolean isPlaying() { + return playback != null && playback.isPlaying(); + } + + public int getPosition() { + return position; + } + + public void setPosition(final int position) { + // handle this on the handlers thread to avoid blocking the ui thread + playerHandler.removeMessages(SET_POSITION); + playerHandler.obtainMessage(SET_POSITION, position, 0).sendToTarget(); + } + + public void playNextSong(boolean force) { + playSongAt(getNextPosition(force)); + } + + private boolean openTrackAndPrepareNextAt(int position) { + synchronized (this) { + this.position = position; + boolean prepared = openCurrent(); + if (prepared) prepareNextImpl(); + notifyChange(META_CHANGED); + notHandledMetaChangedForCurrentTrack = false; + return prepared; + } + } + + private boolean openCurrent() { + synchronized (this) { + try { + return playback.setDataSource(getTrackUri(getCurrentSong())); + } catch (Exception e) { + return false; + } + } + } + + private void prepareNext() { + playerHandler.removeMessages(PREPARE_NEXT); + playerHandler.obtainMessage(PREPARE_NEXT).sendToTarget(); + } + + private boolean prepareNextImpl() { + synchronized (this) { + try { + int nextPosition = getNextPosition(false); + playback.setNextDataSource(getTrackUri(getSongAt(nextPosition))); + this.nextPosition = nextPosition; + return true; + } catch (Exception e) { + return false; + } + } + } + + private void closeAudioEffectSession() { + final Intent audioEffectsIntent = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION); + audioEffectsIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, playback.getAudioSessionId()); + audioEffectsIntent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName()); + sendBroadcast(audioEffectsIntent); + } + + private boolean requestFocus() { + return (getAudioManager().requestAudioFocus(audioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED); + } + + public void initNotification() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && !PreferenceUtil.getInstance().classicNotification()) { + playingNotification = new PlayingNotificationImpl24(); + } else { + playingNotification = new PlayingNotificationOreo(); + } + playingNotification.init(this); + } + + public void updateNotification() { + if (playingNotification != null && getCurrentSong().getId() != -1) { + playingNotification.update(); + } + } + + private void updateMediaSessionPlaybackState() { + mediaSession.setPlaybackState( + new PlaybackStateCompat.Builder() + .setActions(MEDIA_SESSION_ACTIONS) + .setState(isPlaying() ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED, + getPosition(), 1) + .build()); + } + + private void updateMediaSessionMetaData() { + final Song song = getCurrentSong(); + + if (song.getId() == -1) { + mediaSession.setMetadata(null); + return; + } + + final MediaMetadataCompat.Builder metaData = new MediaMetadataCompat.Builder() + .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, song.getArtistName()) + .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, song.getArtistName()) + .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.getAlbumName()) + .putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.getTitle()) + .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.getDuration()) + .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, getPosition() + 1) + .putLong(MediaMetadataCompat.METADATA_KEY_YEAR, song.getYear()) + .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, null); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + metaData.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, getPlayingQueue().size()); + } + + if (PreferenceUtil.getInstance().albumArtOnLockscreen()) { + final Point screenSize = RetroUtil.getScreenSize(MusicService.this); + final BitmapRequestBuilder request = SongGlideRequest.Builder.from(Glide.with(MusicService.this), song) + .checkIgnoreMediaStore(MusicService.this) + .asBitmap().build(); + if (PreferenceUtil.getInstance().blurredAlbumArt()) { + request.transform(new BlurTransformation.Builder(MusicService.this).build()); + } + runOnUiThread(new Runnable() { + @Override + public void run() { + request.into(new SimpleTarget(screenSize.x, screenSize.y) { + @Override + public void onLoadFailed(Exception e, Drawable errorDrawable) { + super.onLoadFailed(e, errorDrawable); + mediaSession.setMetadata(metaData.build()); + } + + @Override + public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) { + metaData.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, copy(resource)); + mediaSession.setMetadata(metaData.build()); + } + }); + } + }); + } else { + mediaSession.setMetadata(metaData.build()); + } + } + + public void runOnUiThread(Runnable runnable) { + uiThreadHandler.post(runnable); + } + + public Song getCurrentSong() { + return getSongAt(getPosition()); + } + + public Song getSongAt(int position) { + if (position >= 0 && position < getPlayingQueue().size()) { + return getPlayingQueue().get(position); + } else { + return Song.Companion.getEmptySong(); + } + } + + public int getNextPosition(boolean force) { + int position = getPosition() + 1; + switch (getRepeatMode()) { + case REPEAT_MODE_ALL: + if (isLastTrack()) { + position = 0; + } + break; + case REPEAT_MODE_THIS: + if (force) { + if (isLastTrack()) { + position = 0; + } + } else { + position -= 1; + } + break; + default: + case REPEAT_MODE_NONE: + if (isLastTrack()) { + position -= 1; + } + break; + } + return position; + } + + private boolean isLastTrack() { + return getPosition() == getPlayingQueue().size() - 1; + } + + public ArrayList getPlayingQueue() { + return playingQueue; + } + + public int getRepeatMode() { + return repeatMode; + } + + public void setRepeatMode(final int repeatMode) { + switch (repeatMode) { + case REPEAT_MODE_NONE: + case REPEAT_MODE_ALL: + case REPEAT_MODE_THIS: + this.repeatMode = repeatMode; + PreferenceManager.getDefaultSharedPreferences(this).edit() + .putInt(SAVED_REPEAT_MODE, repeatMode) + .apply(); + prepareNext(); + handleAndSendChangeInternal(REPEAT_MODE_CHANGED); + break; + } + } + + public void openQueue(@Nullable final ArrayList playingQueue, final int startPosition, final boolean startPlaying) { + if (playingQueue != null && !playingQueue.isEmpty() && startPosition >= 0 && startPosition < playingQueue.size()) { + // it is important to copy the playing queue here first as we might add/remove songs later + originalPlayingQueue = new ArrayList<>(playingQueue); + this.playingQueue = new ArrayList<>(originalPlayingQueue); + + int position = startPosition; + if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { + ShuffleHelper.INSTANCE.makeShuffleList(this.playingQueue, startPosition); + position = 0; + } + if (startPlaying) { + playSongAt(position); + } else { + setPosition(position); + } + notifyChange(QUEUE_CHANGED); + } + } + + public void addSong(int position, Song song) { + playingQueue.add(position, song); + originalPlayingQueue.add(position, song); + notifyChange(QUEUE_CHANGED); + } + + public void addSong(Song song) { + playingQueue.add(song); + originalPlayingQueue.add(song); + notifyChange(QUEUE_CHANGED); + } + + public void addSongs(int position, List songs) { + playingQueue.addAll(position, songs); + originalPlayingQueue.addAll(position, songs); + notifyChange(QUEUE_CHANGED); + } + + public void addSongs(List songs) { + playingQueue.addAll(songs); + originalPlayingQueue.addAll(songs); + notifyChange(QUEUE_CHANGED); + } + + public void removeSong(int position) { + if (getShuffleMode() == SHUFFLE_MODE_NONE) { + playingQueue.remove(position); + originalPlayingQueue.remove(position); + } else { + originalPlayingQueue.remove(playingQueue.remove(position)); + } + + rePosition(position); + + notifyChange(QUEUE_CHANGED); + } + + public void removeSong(@NonNull Song song) { + for (int i = 0; i < playingQueue.size(); i++) { + if (playingQueue.get(i).getId() == song.getId()) { + playingQueue.remove(i); + rePosition(i); + } + } + for (int i = 0; i < originalPlayingQueue.size(); i++) { + if (originalPlayingQueue.get(i).getId() == song.getId()) { + originalPlayingQueue.remove(i); + } + } + notifyChange(QUEUE_CHANGED); + } + + private void rePosition(int deletedPosition) { + int currentPosition = getPosition(); + if (deletedPosition < currentPosition) { + position = currentPosition - 1; + } else if (deletedPosition == currentPosition) { + if (playingQueue.size() > deletedPosition) { + setPosition(position); + } else { + setPosition(position - 1); + } + } + } + + public void moveSong(int from, int to) { + if (from == to) return; + final int currentPosition = getPosition(); + Song songToMove = playingQueue.remove(from); + playingQueue.add(to, songToMove); + if (getShuffleMode() == SHUFFLE_MODE_NONE) { + Song tmpSong = originalPlayingQueue.remove(from); + originalPlayingQueue.add(to, tmpSong); + } + if (from > currentPosition && to <= currentPosition) { + position = currentPosition + 1; + } else if (from < currentPosition && to >= currentPosition) { + position = currentPosition - 1; + } else if (from == currentPosition) { + position = to; + } + notifyChange(QUEUE_CHANGED); + } + + public void clearQueue() { + playingQueue.clear(); + originalPlayingQueue.clear(); + + setPosition(-1); + notifyChange(QUEUE_CHANGED); + } + + public void playSongAt(final int position) { + // handle this on the handlers thread to avoid blocking the ui thread + playerHandler.removeMessages(PLAY_SONG); + playerHandler.obtainMessage(PLAY_SONG, position, 0).sendToTarget(); + } + + private void playSongAtImpl(int position) { + if (openTrackAndPrepareNextAt(position)) { + play(); + } else { + Toast.makeText(this, getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show(); + } + } + + public void pause() { + pausedByTransientLossOfFocus = false; + if (playback.isPlaying()) { + playback.pause(); + notifyChange(PLAY_STATE_CHANGED); + } + } + + public void play() { + synchronized (this) { + if (requestFocus()) { + if (!playback.isPlaying()) { + if (!playback.isInitialized()) { + playSongAt(getPosition()); + } else { + playback.start(); + if (!becomingNoisyReceiverRegistered) { + registerReceiver(becomingNoisyReceiver, becomingNoisyReceiverIntentFilter); + becomingNoisyReceiverRegistered = true; + } + if (notHandledMetaChangedForCurrentTrack) { + handleChangeInternal(META_CHANGED); + notHandledMetaChangedForCurrentTrack = false; + } + notifyChange(PLAY_STATE_CHANGED); + + // fixes a bug where the volume would stay ducked because the AudioManager.AUDIOFOCUS_GAIN event is not sent + playerHandler.removeMessages(DUCK); + playerHandler.sendEmptyMessage(UNDUCK); + } + } + } else { + Toast.makeText(this, getResources().getString(R.string.audio_focus_denied), Toast.LENGTH_SHORT).show(); + } + } + } + + public void playSongs(ArrayList songs, int shuffleMode) { + if (songs != null && !songs.isEmpty()) { + if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { + int startPosition = 0; + if (!songs.isEmpty()) { + startPosition = new Random().nextInt(songs.size()); + } + openQueue(songs, startPosition, false); + setShuffleMode(shuffleMode); + } else { + openQueue(songs, 0, false); + } + play(); + } else { + Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG).show(); + } + } + + public void playPreviousSong(boolean force) { + playSongAt(getPreviousPosition(force)); + } + + public void back(boolean force) { + if (getSongProgressMillis() > 2000) { + seek(0); + } else { + playPreviousSong(force); + } + } + + public int getPreviousPosition(boolean force) { + int newPosition = getPosition() - 1; + switch (repeatMode) { + case REPEAT_MODE_ALL: + if (newPosition < 0) { + newPosition = getPlayingQueue().size() - 1; + } + break; + case REPEAT_MODE_THIS: + if (force) { + if (newPosition < 0) { + newPosition = getPlayingQueue().size() - 1; + } + } else { + newPosition = getPosition(); + } + break; + default: + case REPEAT_MODE_NONE: + if (newPosition < 0) { + newPosition = 0; + } + break; + } + return newPosition; + } + + public int getSongProgressMillis() { + return playback.position(); + } + + public int getSongDurationMillis() { + return playback.duration(); + } + + public long getQueueDurationMillis(int position) { + long duration = 0; + for (int i = position + 1; i < playingQueue.size(); i++) + duration += playingQueue.get(i).getDuration(); + return duration; + } + + public int seek(int millis) { + synchronized (this) { + try { + int newPosition = playback.seek(millis); + throttledSeekHandler.notifySeek(); + return newPosition; + } catch (Exception e) { + return -1; + } + } + } + + public void cycleRepeatMode() { + switch (getRepeatMode()) { + case REPEAT_MODE_NONE: + setRepeatMode(REPEAT_MODE_ALL); + break; + case REPEAT_MODE_ALL: + setRepeatMode(REPEAT_MODE_THIS); + break; + default: + setRepeatMode(REPEAT_MODE_NONE); + break; + } + } + + public void toggleShuffle() { + if (getShuffleMode() == SHUFFLE_MODE_NONE) { + setShuffleMode(SHUFFLE_MODE_SHUFFLE); + } else { + setShuffleMode(SHUFFLE_MODE_NONE); + } + } + + public int getShuffleMode() { + return shuffleMode; + } + + public void setShuffleMode(final int shuffleMode) { + PreferenceManager.getDefaultSharedPreferences(this).edit() + .putInt(SAVED_SHUFFLE_MODE, shuffleMode) + .apply(); + switch (shuffleMode) { + case SHUFFLE_MODE_SHUFFLE: + this.shuffleMode = shuffleMode; + ShuffleHelper.INSTANCE.makeShuffleList(this.getPlayingQueue(), getPosition()); + position = 0; + break; + case SHUFFLE_MODE_NONE: + this.shuffleMode = shuffleMode; + int currentSongId = getCurrentSong().getId(); + playingQueue = new ArrayList<>(originalPlayingQueue); + int newPosition = 0; + for (Song song : getPlayingQueue()) { + if (song.getId() == currentSongId) { + newPosition = getPlayingQueue().indexOf(song); + } + } + position = newPosition; + break; + } + handleAndSendChangeInternal(SHUFFLE_MODE_CHANGED); + notifyChange(QUEUE_CHANGED); + } + + private void notifyChange(@NonNull final String what) { + handleAndSendChangeInternal(what); + sendPublicIntent(what); + } + + private void handleAndSendChangeInternal(@NonNull final String what) { + handleChangeInternal(what); + sendChangeInternal(what); + } + + // to let other apps know whats playing. i.E. last.fm (scrobbling) or musixmatch + private void sendPublicIntent(@NonNull final String what) { + final Intent intent = new Intent(what.replace(RETRO_MUSIC_PACKAGE_NAME, MUSIC_PACKAGE_NAME)); + + final Song song = getCurrentSong(); + + intent.putExtra("id", song.getId()); + intent.putExtra("artist", song.getArtistName()); + intent.putExtra("album", song.getAlbumName()); + intent.putExtra("track", song.getTitle()); + intent.putExtra("duration", song.getDuration()); + intent.putExtra("position", (long) getSongProgressMillis()); + intent.putExtra("playing", isPlaying()); + intent.putExtra("scrobbling_source", RETRO_MUSIC_PACKAGE_NAME); + + sendStickyBroadcast(intent); + + } + + private void sendChangeInternal(final String what) { + sendBroadcast(new Intent(what)); + + } + + private void handleChangeInternal(@NonNull final String what) { + switch (what) { + case PLAY_STATE_CHANGED: + updateNotification(); + updateMediaSessionPlaybackState(); + final boolean isPlaying = isPlaying(); + if (!isPlaying && getSongProgressMillis() > 0) { + savePositionInTrack(); + } + songPlayCountHelper.notifyPlayStateChanged(isPlaying); + break; + case META_CHANGED: + updateNotification(); + updateMediaSessionMetaData(); + savePosition(); + savePositionInTrack(); + final Song currentSong = getCurrentSong(); + HistoryStore.getInstance(this).addSongId(currentSong.getId()); + if (songPlayCountHelper.shouldBumpPlayCount()) { + SongPlayCountStore.getInstance(this).bumpPlayCount(songPlayCountHelper.getSong().getId()); + } + songPlayCountHelper.notifySongChanged(currentSong); + break; + case QUEUE_CHANGED: + updateMediaSessionMetaData(); // because playing queue size might have changed + saveState(); + if (playingQueue.size() > 0) { + prepareNext(); + } else { + playingNotification.stop(); + } + break; + } + } + + public int getAudioSessionId() { + return playback.getAudioSessionId(); + } + + public MediaSessionCompat getMediaSession() { + return mediaSession; + } + + public void releaseWakeLock() { + if (wakeLock.isHeld()) { + wakeLock.release(); + } + } + + public void acquireWakeLock(long milli) { + wakeLock.acquire(milli); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + switch (key) { + case PreferenceUtil.GAPLESS_PLAYBACK: + if (sharedPreferences.getBoolean(key, false)) { + prepareNext(); + } else { + playback.setNextDataSource(null); + } + break; + case PreferenceUtil.ALBUM_ART_ON_LOCKSCREEN: + case PreferenceUtil.BLURRED_ALBUM_ART: + updateMediaSessionMetaData(); + break; + case PreferenceUtil.COLORED_NOTIFICATION: + case PreferenceUtil.DOMINANT_COLOR: + updateNotification(); + break; + case PreferenceUtil.CLASSIC_NOTIFICATION: + initNotification(); + updateNotification(); + break; + case PreferenceUtil.TOGGLE_HEADSET: + registerHeadsetEvents(); + break; + } + } + + private void registerHeadsetEvents() { + if (!headsetReceiverRegistered && PreferenceUtil.getInstance().getHeadsetPlugged()) { + registerReceiver(headsetReceiver, headsetReceiverIntentFilter); + headsetReceiverRegistered = true; + } + } + + @Override + public void onTrackWentToNext() { + playerHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT); + } + + @Override + public void onTrackEnded() { + acquireWakeLock(30000); + playerHandler.sendEmptyMessage(TRACK_ENDED); + } + + + private static final class QueueSaveHandler extends Handler { + @NonNull + private final WeakReference mService; + + QueueSaveHandler(final MusicService service, @NonNull final Looper looper) { + super(looper); + mService = new WeakReference<>(service); + } + + @Override + public void handleMessage(@NonNull Message msg) { + final MusicService service = mService.get(); + switch (msg.what) { + case SAVE_QUEUES: + service.saveQueuesImpl(); + break; + } + } + } + + private static final class PlaybackHandler extends Handler { + @NonNull + private final WeakReference mService; + private float currentDuckVolume = 1.0f; + + PlaybackHandler(final MusicService service, @NonNull final Looper looper) { + super(looper); + mService = new WeakReference<>(service); + } + + @Override + public void handleMessage(@NonNull final Message msg) { + final MusicService service = mService.get(); + if (service == null) { + return; + } + + switch (msg.what) { + case DUCK: + if (PreferenceUtil.getInstance().audioDucking()) { + currentDuckVolume -= .05f; + if (currentDuckVolume > .2f) { + sendEmptyMessageDelayed(DUCK, 10); + } else { + currentDuckVolume = .2f; + } + } else { + currentDuckVolume = 1f; + } + service.playback.setVolume(currentDuckVolume); + break; + + case UNDUCK: + if (PreferenceUtil.getInstance().audioDucking()) { + currentDuckVolume += .03f; + if (currentDuckVolume < 1f) { + sendEmptyMessageDelayed(UNDUCK, 10); + } else { + currentDuckVolume = 1f; + } + } else { + currentDuckVolume = 1f; + } + service.playback.setVolume(currentDuckVolume); + break; + + case TRACK_WENT_TO_NEXT: + if (service.getRepeatMode() == REPEAT_MODE_NONE && service.isLastTrack()) { + service.pause(); + service.seek(0); + } else { + service.position = service.nextPosition; + service.prepareNextImpl(); + service.notifyChange(META_CHANGED); + } + break; + + case TRACK_ENDED: + if (service.getRepeatMode() == REPEAT_MODE_NONE && service.isLastTrack()) { + service.notifyChange(PLAY_STATE_CHANGED); + service.seek(0); + } else { + service.playNextSong(false); + } + sendEmptyMessage(RELEASE_WAKELOCK); + break; + + case RELEASE_WAKELOCK: + service.releaseWakeLock(); + break; + + case PLAY_SONG: + service.playSongAtImpl(msg.arg1); + break; + + case SET_POSITION: + service.openTrackAndPrepareNextAt(msg.arg1); + service.notifyChange(PLAY_STATE_CHANGED); + break; + + case PREPARE_NEXT: + service.prepareNextImpl(); + break; + + case RESTORE_QUEUES: + service.restoreQueuesAndPositionIfNecessary(); + break; + + case FOCUS_CHANGE: + switch (msg.arg1) { + case AudioManager.AUDIOFOCUS_GAIN: + if (!service.isPlaying() && service.pausedByTransientLossOfFocus) { + service.play(); + service.pausedByTransientLossOfFocus = false; + } + removeMessages(DUCK); + sendEmptyMessage(UNDUCK); + break; + + case AudioManager.AUDIOFOCUS_LOSS: + // Lost focus for an unbounded amount of time: stop playback and release media playback + service.pause(); + break; + + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: + // Lost focus for a short time, but we have to stop + // playback. We don't release the media playback because playback + // is likely to resume + boolean wasPlaying = service.isPlaying(); + service.pause(); + service.pausedByTransientLossOfFocus = wasPlaying; + break; + + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: + // Lost focus for a short time, but it's ok to keep playing + // at an attenuated level + removeMessages(UNDUCK); + sendEmptyMessage(DUCK); + break; + } + break; + } + } + } + + private static class SongPlayCountHelper { + public static final String TAG = SongPlayCountHelper.class.getSimpleName(); + + private StopWatch stopWatch = new StopWatch(); + private Song song = Song.Companion.getEmptySong(); + + public Song getSong() { + return song; + } + + boolean shouldBumpPlayCount() { + return song.getDuration() * 0.5d < stopWatch.getElapsedTime(); + } + + void notifySongChanged(Song song) { + synchronized (this) { + stopWatch.reset(); + this.song = song; + } + } + + void notifyPlayStateChanged(boolean isPlaying) { + synchronized (this) { + if (isPlaying) { + stopWatch.start(); + } else { + stopWatch.pause(); + } + } + } + } + + public class MusicBinder extends Binder { + @NonNull + public MusicService getService() { + return MusicService.this; + } + } + + private class MediaStoreObserver extends ContentObserver implements Runnable { + // milliseconds to delay before calling refresh to aggregate events + private static final long REFRESH_DELAY = 500; + private Handler mHandler; + + MediaStoreObserver(Handler handler) { + super(handler); + mHandler = handler; + } + + @Override + public void onChange(boolean selfChange) { + // if a change is detected, remove any scheduled callback + // then post a new one. This is intended to prevent closely + // spaced events from generating multiple refresh calls + mHandler.removeCallbacks(this); + mHandler.postDelayed(this, REFRESH_DELAY); + } + + @Override + public void run() { + // actually call refresh when the delayed callback fires + // do not send a sticky broadcast here + handleAndSendChangeInternal(MEDIA_STORE_CHANGED); + } + } + + private class ThrottledSeekHandler implements Runnable { + // milliseconds to throttle before calling run() to aggregate events + private static final long THROTTLE = 500; + private Handler mHandler; + + ThrottledSeekHandler(Handler handler) { + mHandler = handler; + } + + void notifySeek() { + mHandler.removeCallbacks(this); + mHandler.postDelayed(this, THROTTLE); + } + + @Override + public void run() { + savePositionInTrack(); + sendPublicIntent(PLAY_STATE_CHANGED); // for musixmatch synced lyrics + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/service/MusicService.kt b/app/src/main/java/code/name/monkey/retromusic/service/MusicService.kt deleted file mode 100644 index 813385cf..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/service/MusicService.kt +++ /dev/null @@ -1,1247 +0,0 @@ -package code.name.monkey.retromusic.service - -import android.annotation.SuppressLint -import android.app.PendingIntent -import android.app.Service -import android.content.* -import android.database.ContentObserver -import android.graphics.Bitmap -import android.graphics.drawable.Drawable -import android.media.AudioManager -import android.media.audiofx.AudioEffect -import android.media.session.MediaSession -import android.os.* -import android.preference.PreferenceManager -import android.provider.MediaStore -import android.support.v4.media.MediaMetadataCompat -import android.support.v4.media.session.MediaSessionCompat -import android.support.v4.media.session.PlaybackStateCompat -import android.telephony.PhoneStateListener -import android.telephony.TelephonyManager -import android.util.Log -import android.widget.Toast -import code.name.monkey.retromusic.Constants.ACTION_PAUSE -import code.name.monkey.retromusic.Constants.ACTION_PLAY -import code.name.monkey.retromusic.Constants.ACTION_PLAY_PLAYLIST -import code.name.monkey.retromusic.Constants.ACTION_QUIT -import code.name.monkey.retromusic.Constants.ACTION_SKIP -import code.name.monkey.retromusic.Constants.ACTION_STOP -import code.name.monkey.retromusic.Constants.ACTION_TOGGLE_PAUSE -import code.name.monkey.retromusic.Constants.APP_WIDGET_UPDATE -import code.name.monkey.retromusic.Constants.EXTRA_APP_WIDGET_NAME -import code.name.monkey.retromusic.Constants.INTENT_EXTRA_PLAYLIST -import code.name.monkey.retromusic.Constants.INTENT_EXTRA_SHUFFLE_MODE -import code.name.monkey.retromusic.Constants.MEDIA_STORE_CHANGED -import code.name.monkey.retromusic.Constants.META_CHANGED -import code.name.monkey.retromusic.Constants.MUSIC_PACKAGE_NAME -import code.name.monkey.retromusic.Constants.PLAY_STATE_CHANGED -import code.name.monkey.retromusic.Constants.QUEUE_CHANGED -import code.name.monkey.retromusic.Constants.REPEAT_MODE_CHANGED -import code.name.monkey.retromusic.Constants.RETRO_MUSIC_PACKAGE_NAME -import code.name.monkey.retromusic.Constants.SHUFFLE_MODE_CHANGED -import code.name.monkey.retromusic.R -import code.name.monkey.retromusic.glide.BlurTransformation -import code.name.monkey.retromusic.glide.SongGlideRequest -import code.name.monkey.retromusic.helper.MusicPlayerRemote.setShuffleMode -import code.name.monkey.retromusic.helper.ShuffleHelper -import code.name.monkey.retromusic.helper.StopWatch -import code.name.monkey.retromusic.loaders.PlaylistSongsLoader -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.providers.HistoryStore -import code.name.monkey.retromusic.providers.MusicPlaybackQueueStore -import code.name.monkey.retromusic.providers.SongPlayCountStore -import code.name.monkey.retromusic.service.notification.PlayingNotification -import code.name.monkey.retromusic.service.notification.PlayingNotificationImpl24 -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.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.google.android.gms.cast.framework.media.MediaIntentReceiver.ACTION_REWIND -import java.lang.ref.WeakReference -import java.util.* - -/** - * @author Karim Abou Zeid (kabouzeid), Andrew Neal - */ -class MusicService : Service(), SharedPreferences.OnSharedPreferenceChangeListener, Playback.PlaybackCallbacks { - private val musicBind = MusicBinder() - private var playback: Playback? = null - var playingQueue = ArrayList() - private set - private var originalPlayingQueue = ArrayList() - var position = -1 - set(value) { - playerHandler!!.removeMessages(SET_POSITION) - playerHandler!!.obtainMessage(SET_POSITION, value, 0).sendToTarget() - } - private var nextPosition = -1 - var shuffleMode: Int = 0 - set(value) { - PreferenceManager.getDefaultSharedPreferences(this).edit() - .putInt(SAVED_SHUFFLE_MODE, value) - .apply() - when (value) { - SHUFFLE_MODE_SHUFFLE -> { - field = value - ShuffleHelper.makeShuffleList(this.playingQueue, position) - position = 0 - } - SHUFFLE_MODE_NONE -> { - field = value - val currentSongId = currentSong.id - playingQueue = ArrayList(originalPlayingQueue) - var newPosition = 0 - for (song in playingQueue) { - if (song.id == currentSongId) { - newPosition = playingQueue.indexOf(song) - } - } - position = newPosition - } - } - handleAndSendChangeInternal(SHUFFLE_MODE_CHANGED) - notifyChange(QUEUE_CHANGED) - } - var repeatMode: Int = 0 - set(value) { - when (value) { - REPEAT_MODE_NONE, REPEAT_MODE_ALL, REPEAT_MODE_THIS -> { - field = value - PreferenceManager.getDefaultSharedPreferences(this).edit() - .putInt(SAVED_REPEAT_MODE, repeatMode) - .apply() - prepareNext() - handleAndSendChangeInternal(REPEAT_MODE_CHANGED) - } - } - } - private var queuesRestored: Boolean = false - private var pausedByTransientLossOfFocus: Boolean = false - private val becomingNoisyReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - if (intent.action != null && intent.action == AudioManager.ACTION_AUDIO_BECOMING_NOISY) { - pause() - } - } - } - private var playingNotification: PlayingNotification? = null - private var audioManager: AudioManager? = null - var mediaSession: MediaSessionCompat? = null - private set - private var wakeLock: PowerManager.WakeLock? = null - private var playerHandler: PlaybackHandler? = null - private val audioFocusListener = AudioManager.OnAudioFocusChangeListener { focusChange -> playerHandler!!.obtainMessage(FOCUS_CHANGE, focusChange, 0).sendToTarget() } - private var queueSaveHandler: QueueSaveHandler? = null - private var musicPlayerHandlerThread: HandlerThread? = null - private var queueSaveHandlerThread: HandlerThread? = null - private val songPlayCountHelper = SongPlayCountHelper() - private var throttledSeekHandler: ThrottledSeekHandler? = null - private var becomingNoisyReceiverRegistered: Boolean = false - private val becomingNoisyReceiverIntentFilter = IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY) - private var mediaStoreObserver: ContentObserver? = null - private var notHandledMetaChangedForCurrentTrack: Boolean = false - private val phoneStateListener = object : PhoneStateListener() { - override fun onCallStateChanged(state: Int, incomingNumber: String) { - when (state) { - TelephonyManager.CALL_STATE_IDLE -> - //Not in call: Play music - play() - TelephonyManager.CALL_STATE_RINGING, TelephonyManager.CALL_STATE_OFFHOOK -> - //A call is dialing, active or on hold - pause() - } - super.onCallStateChanged(state, incomingNumber) - } - } - private var isServiceBound: Boolean = false - private var uiThreadHandler: Handler? = null - private val widgetIntentReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - val command = intent.getStringExtra(EXTRA_APP_WIDGET_NAME) - } - } - private val headsetReceiverIntentFilter = IntentFilter(Intent.ACTION_HEADSET_PLUG) - private var headsetReceiverRegistered = false - private val headsetReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - val action = intent.action - if (action != null) { - when (action) { - Intent.ACTION_HEADSET_PLUG -> { - val state = intent.getIntExtra("state", -1) - when (state) { - 0 -> { - Log.d(TAG, "Headset unplugged") - pause() - } - 1 -> { - Log.d(TAG, "Headset plugged") - play() - } - } - } - } - } - } - } - - val isPlaying: Boolean - get() = playback != null && playback!!.isPlaying - - val currentSong: Song - get() = getSongAt(position) - - private val isLastTrack: Boolean - get() = position == playingQueue.size - 1 - - val songProgressMillis: Int - get() = playback!!.position() - - val songDurationMillis: Int - get() = playback!!.duration() - - val audioSessionId: Int - get() = playback!!.audioSessionId - - - override fun onCreate() { - super.onCreate() - - val telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager - telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE) - - val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager - wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, javaClass.name) - wakeLock!!.setReferenceCounted(false) - - musicPlayerHandlerThread = HandlerThread("PlaybackHandler") - musicPlayerHandlerThread!!.start() - playerHandler = PlaybackHandler(this, musicPlayerHandlerThread!!.looper) - - playback = MultiPlayer(this) - playback!!.setCallbacks(this) - - setupMediaSession() - - // queue saving needs to run on a separate thread so that it doesn't block the playback handler events - queueSaveHandlerThread = HandlerThread("QueueSaveHandler", Process.THREAD_PRIORITY_BACKGROUND) - queueSaveHandlerThread!!.start() - queueSaveHandler = QueueSaveHandler(this, queueSaveHandlerThread!!.looper) - - uiThreadHandler = Handler() - - registerReceiver(widgetIntentReceiver, IntentFilter(APP_WIDGET_UPDATE)) - - initNotification() - - mediaStoreObserver = MediaStoreObserver(playerHandler!!) - throttledSeekHandler = ThrottledSeekHandler(playerHandler!!) - contentResolver.registerContentObserver( - MediaStore.Audio.Media.INTERNAL_CONTENT_URI, true, mediaStoreObserver!!) - contentResolver.registerContentObserver( - MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, true, mediaStoreObserver!!) - - PreferenceUtil.getInstance().registerOnSharedPreferenceChangedListener(this) - - restoreState() - - mediaSession!!.isActive = true - - sendBroadcast(Intent("code.name.monkey.retromusic.RETRO_MUSIC_SERVICE_CREATED")) - - registerHeadsetEvents() - - - } - - private fun getAudioManager(): AudioManager? { - if (audioManager == null) { - audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager - } - return audioManager - } - - @SuppressLint("WrongConstant") - private fun setupMediaSession() { - val mediaButtonReceiverComponentName = ComponentName(applicationContext, MediaButtonIntentReceiver::class.java) - - val mediaButtonIntent = Intent(Intent.ACTION_MEDIA_BUTTON) - mediaButtonIntent.component = mediaButtonReceiverComponentName - - - val mediaButtonReceiverPendingIntent = PendingIntent.getBroadcast(applicationContext, 0, mediaButtonIntent, 0) - - mediaSession = MediaSessionCompat(this, "RetroMusicPlayer", mediaButtonReceiverComponentName, mediaButtonReceiverPendingIntent) - mediaSession!!.setCallback(object : MediaSessionCompat.Callback() { - override fun onPlay() { - play() - } - - override fun onPause() { - pause() - } - - override fun onSkipToNext() { - playNextSong(true) - } - - override fun onSkipToPrevious() { - back(true) - } - - override fun onStop() { - quit() - } - - override fun onSeekTo(pos: Long) { - seek(pos.toInt()) - } - - override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean { - return MediaButtonIntentReceiver.handleIntent(this@MusicService, mediaButtonEvent) - } - }) - - mediaSession!!.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS or MediaSession.FLAG_HANDLES_MEDIA_BUTTONS) - - mediaSession!!.setMediaButtonReceiver(mediaButtonReceiverPendingIntent) - } - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - if (intent != null) { - if (intent.action != null) { - restoreQueuesAndPositionIfNecessary() - val action = intent.action - when (action) { - ACTION_TOGGLE_PAUSE -> if (isPlaying) { - pause() - } else { - play() - } - ACTION_PAUSE -> pause() - ACTION_PLAY -> play() - ACTION_PLAY_PLAYLIST -> { - val playlist = intent.getParcelableExtra(INTENT_EXTRA_PLAYLIST) - val shuffleMode = intent.getIntExtra(INTENT_EXTRA_SHUFFLE_MODE, shuffleMode) - if (playlist != null) { - val playlistSongs: ArrayList - if (playlist is AbsCustomPlaylist) { - playlistSongs = playlist.getSongs(applicationContext).blockingFirst() - } else { - - playlistSongs = PlaylistSongsLoader.getPlaylistSongList(applicationContext, playlist.id).blockingFirst() - } - if (!playlistSongs.isEmpty()) { - if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { - val startPosition: Int = Random().nextInt(playlistSongs.size) - openQueue(playlistSongs, startPosition, true) - setShuffleMode(shuffleMode) - } else { - openQueue(playlistSongs, 0, true) - } - } else { - Toast.makeText(applicationContext, R.string.playlist_is_empty, Toast.LENGTH_LONG).show() - } - } else { - Toast.makeText(applicationContext, R.string.playlist_is_empty, Toast.LENGTH_LONG).show() - } - } - ACTION_REWIND -> back(true) - ACTION_SKIP -> playNextSong(true) - ACTION_STOP, ACTION_QUIT -> return quit() - } - } - } - - return Service.START_STICKY - } - - - override fun onDestroy() { - unregisterReceiver(widgetIntentReceiver) - if (becomingNoisyReceiverRegistered) { - unregisterReceiver(becomingNoisyReceiver) - becomingNoisyReceiverRegistered = false - } - if (headsetReceiverRegistered) { - unregisterReceiver(headsetReceiver) - headsetReceiverRegistered = false - } - mediaSession!!.isActive = false - quit() - releaseResources() - contentResolver.unregisterContentObserver(mediaStoreObserver!!) - PreferenceUtil.getInstance().unregisterOnSharedPreferenceChangedListener(this) - wakeLock!!.release() - - sendBroadcast(Intent("code.name.monkey.retromusic.RETRO_MUSIC_MUSIC_SERVICE_DESTROYED")) - } - - override fun onBind(intent: Intent): IBinder? { - isServiceBound = true - return musicBind - } - - override fun onRebind(intent: Intent) { - isServiceBound = true - } - - override fun onUnbind(intent: Intent): Boolean { - isServiceBound = false - if (!isPlaying) { - stopSelf() - } - return true - } - - private fun saveQueuesImpl() { - MusicPlaybackQueueStore.getInstance(this).saveQueues(playingQueue, originalPlayingQueue) - } - - private fun savePosition() { - PreferenceManager.getDefaultSharedPreferences(this).edit().putInt(SAVED_POSITION, position).apply() - } - - private fun savePositionInTrack() { - PreferenceManager.getDefaultSharedPreferences(this).edit().putInt(SAVED_POSITION_IN_TRACK, songProgressMillis).apply() - } - - private fun saveState() { - saveQueues() - savePosition() - savePositionInTrack() - } - - private fun saveQueues() { - queueSaveHandler!!.removeMessages(SAVE_QUEUES) - queueSaveHandler!!.sendEmptyMessage(SAVE_QUEUES) - } - - private fun restoreState() { - shuffleMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_SHUFFLE_MODE, 0) - repeatMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_REPEAT_MODE, 0) - handleAndSendChangeInternal(SHUFFLE_MODE_CHANGED) - handleAndSendChangeInternal(REPEAT_MODE_CHANGED) - - playerHandler!!.removeMessages(RESTORE_QUEUES) - playerHandler!!.sendEmptyMessage(RESTORE_QUEUES) - } - - @Synchronized - private fun restoreQueuesAndPositionIfNecessary() { - if (!queuesRestored && playingQueue.isEmpty()) { - val restoredQueue = MusicPlaybackQueueStore.getInstance(this).savedPlayingQueue - .blockingFirst() - - val restoredOriginalQueue = MusicPlaybackQueueStore.getInstance(this).savedOriginalPlayingQueue - .blockingFirst() - - val restoredPosition = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_POSITION, -1) - val restoredPositionInTrack = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_POSITION_IN_TRACK, -1) - - if (restoredQueue.size > 0 && restoredQueue.size == restoredOriginalQueue.size && restoredPosition != -1) { - this.originalPlayingQueue = restoredOriginalQueue - this.playingQueue = restoredQueue - - position = restoredPosition - openCurrent() - prepareNext() - - if (restoredPositionInTrack > 0) seek(restoredPositionInTrack) - - notHandledMetaChangedForCurrentTrack = true - sendChangeInternal(META_CHANGED) - sendChangeInternal(QUEUE_CHANGED) - } - } - queuesRestored = true - } - - @SuppressLint("WrongConstant") - private fun quit(): Int { - pause() - playingNotification!!.stop() - - if (isServiceBound) { - return Service.START_STICKY - } else { - closeAudioEffectSession() - getAudioManager()!!.abandonAudioFocus(audioFocusListener) - stopSelf() - return Service.START_NOT_STICKY - } - } - - private fun releaseResources() { - playerHandler!!.removeCallbacksAndMessages(null) - musicPlayerHandlerThread!!.quitSafely() - queueSaveHandler!!.removeCallbacksAndMessages(null) - queueSaveHandlerThread!!.quitSafely() - playback!!.release() - playback = null - mediaSession!!.release() - } - - fun playNextSong(force: Boolean) { - playSongAt(getNextPosition(force)) - } - - private fun openTrackAndPrepareNextAt(position: Int): Boolean { - synchronized(this) { - this.position = position - val prepared = openCurrent() - if (prepared) prepareNextImpl() - notifyChange(META_CHANGED) - notHandledMetaChangedForCurrentTrack = false - return prepared - } - } - - private fun openCurrent(): Boolean { - synchronized(this) { - try { - return playback!!.setDataSource(getTrackUri(currentSong)) - } catch (e: Exception) { - return false - } - - } - } - - private fun prepareNext() { - playerHandler!!.removeMessages(PREPARE_NEXT) - playerHandler!!.obtainMessage(PREPARE_NEXT).sendToTarget() - } - - private fun prepareNextImpl(): Boolean { - synchronized(this) { - return try { - val nextPosition = getNextPosition(false) - playback!!.setNextDataSource(getTrackUri(getSongAt(nextPosition))) - this.nextPosition = nextPosition - true - } catch (e: Exception) { - false - } - - } - } - - private fun closeAudioEffectSession() { - val audioEffectsIntent = Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION) - audioEffectsIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, playback!!.audioSessionId) - audioEffectsIntent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, packageName) - sendBroadcast(audioEffectsIntent) - } - - private fun requestFocus(): Boolean { - return getAudioManager()!!.requestAudioFocus(audioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED - } - - fun initNotification() { - playingNotification = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && !PreferenceUtil.getInstance().classicNotification()) { - PlayingNotificationImpl24() - } else { - PlayingNotificationOreo() - } - playingNotification!!.init(this) - } - - fun updateNotification() { - if (playingNotification != null && currentSong.id != -1) { - playingNotification!!.update() - } - } - - private fun updateMediaSessionPlaybackState() { - mediaSession!!.setPlaybackState( - PlaybackStateCompat.Builder() - .setActions(MEDIA_SESSION_ACTIONS) - .setState(if (isPlaying) PlaybackStateCompat.STATE_PLAYING else PlaybackStateCompat.STATE_PAUSED, - position.toLong(), 1f) - .build()) - } - - private fun updateMediaSessionMetaData() { - val song = currentSong - - if (song.id == -1) { - mediaSession!!.setMetadata(null) - return - } - - val metaData = MediaMetadataCompat.Builder() - .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, song.artistName) - .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, song.artistName) - .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.albumName) - .putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.title) - .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.duration) - .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, (position + 1).toLong()) - .putLong(MediaMetadataCompat.METADATA_KEY_YEAR, song.year.toLong()) - .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, null) - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - metaData.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, playingQueue.size.toLong()) - } - - if (PreferenceUtil.getInstance().albumArtOnLockscreen()) { - val screenSize = RetroUtil.getScreenSize(this@MusicService) - val request = SongGlideRequest.Builder.from(Glide.with(this@MusicService), song) - .checkIgnoreMediaStore(this@MusicService) - .asBitmap().build() - if (PreferenceUtil.getInstance().blurredAlbumArt()) { - request.transform(BlurTransformation.Builder(this@MusicService).build()) - } - runOnUiThread(Runnable { - request.into(object : SimpleTarget(screenSize.x, screenSize.y) { - override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) { - super.onLoadFailed(e, errorDrawable) - mediaSession!!.setMetadata(metaData.build()) - } - - override fun onResourceReady(resource: Bitmap, glideAnimation: GlideAnimation) { - metaData.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, copy(resource)) - mediaSession!!.setMetadata(metaData.build()) - } - }) - }) - } else { - mediaSession!!.setMetadata(metaData.build()) - } - } - - fun runOnUiThread(runnable: Runnable) { - uiThreadHandler!!.post(runnable) - } - - private fun getSongAt(position: Int): Song { - return if (position >= 0 && position < playingQueue.size) { - playingQueue[position] - } else { - Song.EMPTY_SONG - } - } - - private fun getNextPosition(force: Boolean): Int { - var position = position + 1 - when (repeatMode) { - REPEAT_MODE_ALL -> if (isLastTrack) { - position = 0 - } - REPEAT_MODE_THIS -> if (force) { - if (isLastTrack) { - position = 0 - } - } else { - position -= 1 - } - REPEAT_MODE_NONE -> if (isLastTrack) { - position -= 1 - } - else -> if (isLastTrack) { - position -= 1 - } - } - return position - } - - - fun openQueue(playingQueue: ArrayList?, startPosition: Int, startPlaying: Boolean) { - if (playingQueue != null && !playingQueue.isEmpty() && startPosition >= 0 && startPosition < playingQueue.size) { - // it is important to copy the playing queue here first as we might add/remove songs later - originalPlayingQueue = ArrayList(playingQueue) - this.playingQueue = ArrayList(originalPlayingQueue) - - var position = startPosition - if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { - ShuffleHelper.makeShuffleList(this.playingQueue, startPosition) - position = 0 - } - if (startPlaying) { - playSongAt(position) - } else { - this.position = position - } - notifyChange(QUEUE_CHANGED) - } - } - - fun addSong(position: Int, song: Song) { - playingQueue.add(position, song) - originalPlayingQueue.add(position, song) - notifyChange(QUEUE_CHANGED) - } - - fun addSong(song: Song) { - playingQueue.add(song) - originalPlayingQueue.add(song) - notifyChange(QUEUE_CHANGED) - } - - fun addSongs(position: Int, songs: List) { - playingQueue.addAll(position, songs) - originalPlayingQueue.addAll(position, songs) - notifyChange(QUEUE_CHANGED) - } - - fun addSongs(songs: List) { - playingQueue.addAll(songs) - originalPlayingQueue.addAll(songs) - notifyChange(QUEUE_CHANGED) - } - - fun removeSong(position: Int) { - if (shuffleMode == SHUFFLE_MODE_NONE) { - playingQueue.removeAt(position) - originalPlayingQueue.removeAt(position) - } else { - originalPlayingQueue.remove(playingQueue.removeAt(position)) - } - - rePosition(position) - - notifyChange(QUEUE_CHANGED) - } - - fun removeSong(song: Song) { - for (i in playingQueue.indices) { - if (playingQueue[i].id == song.id) { - playingQueue.removeAt(i) - rePosition(i) - } - } - for (i in originalPlayingQueue.indices) { - if (originalPlayingQueue[i].id == song.id) { - originalPlayingQueue.removeAt(i) - } - } - notifyChange(QUEUE_CHANGED) - } - - private fun rePosition(deletedPosition: Int) { - val currentPosition = position - if (deletedPosition < currentPosition) { - position = currentPosition - 1 - } else if (deletedPosition == currentPosition) { - if (playingQueue.size > deletedPosition) { - this.position = position - } else { - this.position = position - 1 - } - } - } - - fun moveSong(from: Int, to: Int) { - if (from == to) return - val currentPosition = position - val songToMove = playingQueue.removeAt(from) - playingQueue.add(to, songToMove) - if (shuffleMode == SHUFFLE_MODE_NONE) { - val tmpSong = originalPlayingQueue.removeAt(from) - originalPlayingQueue.add(to, tmpSong) - } - if (currentPosition in to..(from - 1)) { - position = currentPosition + 1 - } else if (currentPosition in (from + 1)..to) { - position = currentPosition - 1 - } else if (from == currentPosition) { - position = to - } - notifyChange(QUEUE_CHANGED) - } - - fun clearQueue() { - playingQueue.clear() - originalPlayingQueue.clear() - - position = -1 - notifyChange(QUEUE_CHANGED) - } - - fun playSongAt(position: Int) { - // handle this on the handlers thread to avoid blocking the ui thread - playerHandler!!.removeMessages(PLAY_SONG) - playerHandler!!.obtainMessage(PLAY_SONG, position, 0).sendToTarget() - } - - private fun playSongAtImpl(position: Int) { - if (openTrackAndPrepareNextAt(position)) { - play() - } else { - Toast.makeText(this, resources.getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show() - } - } - - fun pause() { - pausedByTransientLossOfFocus = false - if (playback!!.isPlaying) { - playback!!.pause() - notifyChange(PLAY_STATE_CHANGED) - } - } - - fun play() { - synchronized(this) { - if (requestFocus()) { - if (!playback!!.isPlaying) { - if (!playback!!.isInitialized) { - playSongAt(position) - } else { - playback!!.start() - if (!becomingNoisyReceiverRegistered) { - registerReceiver(becomingNoisyReceiver, becomingNoisyReceiverIntentFilter) - becomingNoisyReceiverRegistered = true - } - if (notHandledMetaChangedForCurrentTrack) { - handleChangeInternal(META_CHANGED) - notHandledMetaChangedForCurrentTrack = false - } - notifyChange(PLAY_STATE_CHANGED) - - // fixes a bug where the volume would stay ducked because the AudioManager.AUDIOFOCUS_GAIN event is not sent - playerHandler!!.removeMessages(DUCK) - playerHandler!!.sendEmptyMessage(UN_DUCK) - } - } - } else { - Toast.makeText(this, resources.getString(R.string.audio_focus_denied), Toast.LENGTH_SHORT).show() - } - } - } - - fun playSongs(songs: ArrayList?, shuffleMode: Int) { - if (songs != null && !songs.isEmpty()) { - if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { - var startPosition = 0 - if (!songs.isEmpty()) { - startPosition = Random().nextInt(songs.size) - } - openQueue(songs, startPosition, false) - setShuffleMode(shuffleMode) - } else { - openQueue(songs, 0, false) - } - play() - } else { - Toast.makeText(applicationContext, R.string.playlist_is_empty, Toast.LENGTH_LONG).show() - } - } - - fun playPreviousSong(force: Boolean) { - playSongAt(getPreviousPosition(force)) - } - - fun back(force: Boolean) { - if (songProgressMillis > 2000) { - seek(0) - } else { - playPreviousSong(force) - } - } - - private fun getPreviousPosition(force: Boolean): Int { - var newPosition = position - 1 - when (repeatMode) { - REPEAT_MODE_ALL -> if (newPosition < 0) { - newPosition = playingQueue.size - 1 - } - REPEAT_MODE_THIS -> if (force) { - if (newPosition < 0) { - newPosition = playingQueue.size - 1 - } - } else { - newPosition = position - } - REPEAT_MODE_NONE -> if (newPosition < 0) { - newPosition = 0 - } - else -> if (newPosition < 0) { - newPosition = 0 - } - } - return newPosition - } - - fun getQueueDurationMillis(position: Int): Long { - var duration: Long = 0 - for (i in position + 1 until playingQueue.size) - duration += playingQueue[i].duration - return duration - } - - fun seek(millis: Int): Int { - synchronized(this) { - try { - val newPosition = playback!!.seek(millis) - throttledSeekHandler!!.notifySeek() - return newPosition - } catch (e: Exception) { - return -1 - } - - } - } - - fun cycleRepeatMode() { - repeatMode = when (repeatMode) { - REPEAT_MODE_NONE -> REPEAT_MODE_ALL - REPEAT_MODE_ALL -> REPEAT_MODE_THIS - else -> REPEAT_MODE_NONE - } - } - - fun toggleShuffle() { - if (shuffleMode == SHUFFLE_MODE_NONE) { - setShuffleMode(SHUFFLE_MODE_SHUFFLE) - } else { - setShuffleMode(SHUFFLE_MODE_NONE) - } - } - - - private fun notifyChange(what: String) { - handleAndSendChangeInternal(what) - sendPublicIntent(what) - } - - private fun handleAndSendChangeInternal(what: String) { - handleChangeInternal(what) - sendChangeInternal(what) - } - - // to let other apps know whats playing. i.E. last.fm (scrobbling) or musixmatch - @SuppressLint("WrongConstant") - private fun sendPublicIntent(what: String) { - val intent = Intent(what.replace(RETRO_MUSIC_PACKAGE_NAME, MUSIC_PACKAGE_NAME)) - - val song = currentSong - - intent.putExtra("id", song.id) - intent.putExtra("artist", song.artistName) - intent.putExtra("album", song.albumName) - intent.putExtra("track", song.title) - intent.putExtra("duration", song.duration) - intent.putExtra("position", songProgressMillis.toLong()) - intent.putExtra("playing", isPlaying) - intent.putExtra("scrobbling_source", RETRO_MUSIC_PACKAGE_NAME) - - sendStickyBroadcast(intent) - - } - - private fun sendChangeInternal(what: String) { - sendBroadcast(Intent(what)) - } - - private fun handleChangeInternal(what: String) { - when (what) { - PLAY_STATE_CHANGED -> { - updateNotification() - updateMediaSessionPlaybackState() - val isPlaying = isPlaying - if (!isPlaying && songProgressMillis > 0) { - savePositionInTrack() - } - songPlayCountHelper.notifyPlayStateChanged(isPlaying) - } - META_CHANGED -> { - updateNotification() - updateMediaSessionMetaData() - savePosition() - savePositionInTrack() - val currentSong = currentSong - HistoryStore.getInstance(this).addSongId(currentSong.id.toLong()) - if (songPlayCountHelper.shouldBumpPlayCount()) { - SongPlayCountStore.getInstance(this).bumpPlayCount(songPlayCountHelper.song.id.toLong()) - } - songPlayCountHelper.notifySongChanged(currentSong) - } - QUEUE_CHANGED -> { - updateMediaSessionMetaData() // because playing queue size might have changed - saveState() - if (playingQueue.size > 0) { - prepareNext() - } else { - playingNotification!!.stop() - } - } - } - } - - fun releaseWakeLock() { - if (wakeLock!!.isHeld) { - wakeLock!!.release() - } - } - - private fun acquireWakeLock(milli: Long) { - wakeLock!!.acquire(milli) - } - - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { - when (key) { - PreferenceUtil.GAPLESS_PLAYBACK -> if (sharedPreferences.getBoolean(key, false)) { - prepareNext() - } else { - playback!!.setNextDataSource(null) - } - PreferenceUtil.ALBUM_ART_ON_LOCKSCREEN, PreferenceUtil.BLURRED_ALBUM_ART -> updateMediaSessionMetaData() - PreferenceUtil.COLORED_NOTIFICATION, PreferenceUtil.DOMINANT_COLOR -> updateNotification() - PreferenceUtil.CLASSIC_NOTIFICATION -> { - initNotification() - updateNotification() - } - PreferenceUtil.TOGGLE_HEADSET -> registerHeadsetEvents() - } - } - - private fun registerHeadsetEvents() { - if (!headsetReceiverRegistered && PreferenceUtil.getInstance().headsetPlugged) { - registerReceiver(headsetReceiver, headsetReceiverIntentFilter) - headsetReceiverRegistered = true - } - } - - override fun onTrackWentToNext() { - playerHandler!!.sendEmptyMessage(TRACK_WENT_TO_NEXT) - } - - override fun onTrackEnded() { - acquireWakeLock(30000) - playerHandler!!.sendEmptyMessage(TRACK_ENDED) - } - - - inner class QueueSaveHandler internal constructor(service: MusicService, looper: Looper) : Handler(looper) { - val mService: WeakReference = WeakReference(service) - - override fun handleMessage(msg: Message) { - val service = mService.get() - when (msg.what) { - SAVE_QUEUES -> service!!.saveQueuesImpl() - } - } - } - - inner class PlaybackHandler internal constructor(service: MusicService, looper: Looper) : Handler(looper) { - val mService: WeakReference = WeakReference(service) - var currentDuckVolume = 1.0f - - override fun handleMessage(msg: Message) { - val service = mService.get() ?: return - - when (msg.what) { - DUCK -> { - if (PreferenceUtil.getInstance().audioDucking()) { - currentDuckVolume -= .05f - if (currentDuckVolume > .2f) { - sendEmptyMessageDelayed(DUCK, 10) - } else { - currentDuckVolume = .2f - } - } else { - currentDuckVolume = 1f - } - service.playback!!.setVolume(currentDuckVolume) - } - - UN_DUCK -> { - if (PreferenceUtil.getInstance().audioDucking()) { - currentDuckVolume += .03f - if (currentDuckVolume < 1f) { - sendEmptyMessageDelayed(UN_DUCK, 10) - } else { - currentDuckVolume = 1f - } - } else { - currentDuckVolume = 1f - } - service.playback!!.setVolume(currentDuckVolume) - } - - TRACK_WENT_TO_NEXT -> if (service.repeatMode == REPEAT_MODE_NONE && service.isLastTrack) { - service.pause() - service.seek(0) - } else { - service.position = service.nextPosition - service.prepareNextImpl() - service.notifyChange(META_CHANGED) - } - - TRACK_ENDED -> { - if (service.repeatMode == REPEAT_MODE_NONE && service.isLastTrack) { - service.notifyChange(PLAY_STATE_CHANGED) - service.seek(0) - } else { - service.playNextSong(false) - } - sendEmptyMessage(RELEASE_WAKELOCK) - } - - RELEASE_WAKELOCK -> service.releaseWakeLock() - - PLAY_SONG -> service.playSongAtImpl(msg.arg1) - - SET_POSITION -> { - service.openTrackAndPrepareNextAt(msg.arg1) - service.notifyChange(PLAY_STATE_CHANGED) - } - - PREPARE_NEXT -> service.prepareNextImpl() - - RESTORE_QUEUES -> service.restoreQueuesAndPositionIfNecessary() - - FOCUS_CHANGE -> when (msg.arg1) { - AudioManager.AUDIOFOCUS_GAIN -> { - if (!service.isPlaying && service.pausedByTransientLossOfFocus) { - service.play() - service.pausedByTransientLossOfFocus = false - } - removeMessages(DUCK) - sendEmptyMessage(UN_DUCK) - } - - AudioManager.AUDIOFOCUS_LOSS -> - // Lost focus for an unbounded amount of time: stop playback and release media playback - service.pause() - - AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> { - // Lost focus for a short time, but we have to stop - // playback. We don't release the media playback because playback - // is likely to resume - val wasPlaying = service.isPlaying - service.pause() - service.pausedByTransientLossOfFocus = wasPlaying - } - - AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> { - // Lost focus for a short time, but it's ok to keep playing - // at an attenuated level - removeMessages(UN_DUCK) - sendEmptyMessage(DUCK) - } - } - } - } - } - - inner class SongPlayCountHelper { - - private val stopWatch = StopWatch() - var song = Song.EMPTY_SONG - private set - - internal fun shouldBumpPlayCount(): Boolean { - return song.duration * 0.5 < stopWatch.elapsedTime - } - - internal fun notifySongChanged(song: Song) { - synchronized(this) { - stopWatch.reset() - this.song = song - } - } - - internal fun notifyPlayStateChanged(isPlaying: Boolean) { - synchronized(this) { - if (isPlaying) { - stopWatch.start() - } else { - stopWatch.pause() - } - } - } - } - - inner class MusicBinder : Binder() { - val service: MusicService - get() = this@MusicService - } - - inner class MediaStoreObserver internal constructor(private val mHandler: Handler) : ContentObserver(mHandler), Runnable { - - override fun onChange(selfChange: Boolean) { - // if a change is detected, remove any scheduled callback - // then post a new one. This is intended to prevent closely - // spaced events from generating multiple refresh calls - mHandler.removeCallbacks(this) - mHandler.postDelayed(this, 500) - } - - override fun run() { - // actually call refresh when the delayed callback fires - // do not send a sticky broadcast here - handleAndSendChangeInternal(MEDIA_STORE_CHANGED) - } - } - - inner class ThrottledSeekHandler internal constructor(private val mHandler: Handler) : Runnable { - - internal fun notifySeek() { - mHandler.removeCallbacks(this) - mHandler.postDelayed(this, 500) - } - - override fun run() { - savePositionInTrack() - sendPublicIntent(PLAY_STATE_CHANGED) // for musixmatch synced lyrics - } - } - - companion object { - val TAG: String = MusicService::class.java.simpleName - - const val SAVED_POSITION = "POSITION" - const val SAVED_POSITION_IN_TRACK = "POSITION_IN_TRACK" - const val SAVED_SHUFFLE_MODE = "SHUFFLE_MODE" - const val SAVED_REPEAT_MODE = "REPEAT_MODE" - - const val RELEASE_WAKELOCK = 0 - const val TRACK_ENDED = 1 - const val TRACK_WENT_TO_NEXT = 2 - const val PLAY_SONG = 3 - const val PREPARE_NEXT = 4 - const val SET_POSITION = 5 - const val RESTORE_QUEUES = 9 - const val SHUFFLE_MODE_NONE = 0 - const val SHUFFLE_MODE_SHUFFLE = 1 - const val REPEAT_MODE_NONE = 0 - const val REPEAT_MODE_ALL = 1 - const val REPEAT_MODE_THIS = 2 - const val SAVE_QUEUES = 0 - const val FOCUS_CHANGE = 6 - const val DUCK = 7 - const val UN_DUCK = 8 - const val MEDIA_SESSION_ACTIONS = (PlaybackStateCompat.ACTION_PLAY - or PlaybackStateCompat.ACTION_PAUSE - or PlaybackStateCompat.ACTION_PLAY_PAUSE - or PlaybackStateCompat.ACTION_SKIP_TO_NEXT - or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS - or PlaybackStateCompat.ACTION_STOP - or PlaybackStateCompat.ACTION_SEEK_TO) - - private fun getTrackUri(song: Song): String { - return MusicUtil.getSongFileUri(song.id).toString() - } - - private fun copy(bitmap: Bitmap): Bitmap? { - var config: Bitmap.Config? = bitmap.config - if (config == null) { - config = Bitmap.Config.RGB_565 - } - try { - return bitmap.copy(config, false) - } catch (e: OutOfMemoryError) { - e.printStackTrace() - return null - } - - } - } -} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/AlbumDetailsActivity.kt b/app/src/main/java/code/name/monkey/retromusic/ui/activities/AlbumDetailsActivity.kt index af628a2c..1876a809 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/activities/AlbumDetailsActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/AlbumDetailsActivity.kt @@ -13,7 +13,6 @@ import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import butterknife.ButterKnife -import butterknife.OnClick import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.TintHelper @@ -49,9 +48,9 @@ import java.util.* class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), AlbumDetailsContract.AlbumDetailsView { - private var albumDetailsPresenter: AlbumDetailsPresenter? = null + private lateinit var albumDetailsPresenter: AlbumDetailsPresenter + private lateinit var simpleSongAdapter: SimpleSongAdapter - private var adapter: SimpleSongAdapter? = null var album: Album? = null private set @@ -82,13 +81,12 @@ class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), AlbumDetailsContrac val albumId = intent.getIntExtra(EXTRA_ALBUM_ID, -1) albumDetailsPresenter = AlbumDetailsPresenter(this, albumId) - albumDetailsPresenter!!.subscribe() + albumDetailsPresenter.subscribe() setupRecyclerView() setupToolbarMarginHeight() - - contentContainer.setOnScrollChangeListener { v: NestedScrollView?, scrollX: Int, scrollY: Int, oldScrollX: Int, oldScrollY: Int -> + contentContainer.setOnScrollChangeListener { _: NestedScrollView?, _: Int, scrollY: Int, _: Int, oldScrollY: Int -> run { if (scrollY > oldScrollY) { actionShuffleAll!!.setShowTitle(false) @@ -98,31 +96,40 @@ class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), AlbumDetailsContrac } } } + + actionShuffleAll.setOnClickListener { MusicPlayerRemote.openAndShuffleQueue(album!!.songs!!, true) } + artistImage.setOnClickListener { + val artistPairs = arrayOf>(Pair.create(image, resources.getString(R.string.transition_artist_image))) + NavigationUtil.goToArtist(this, album!!.artistId, *artistPairs) + } } private fun setupRecyclerView() { - adapter = SimpleSongAdapter(this, ArrayList(), R.layout.item_song) + simpleSongAdapter = SimpleSongAdapter(this, ArrayList(), R.layout.item_song) recyclerView.apply { layoutManager = LinearLayoutManager(this@AlbumDetailsActivity) itemAnimator = DefaultItemAnimator() isNestedScrollingEnabled = false - adapter = adapter + adapter = simpleSongAdapter } } private fun setupToolbarMarginHeight() { - val primaryColor = ThemeStore.primaryColor(this) - TintHelper.setTintAuto(contentContainer!!, primaryColor, true) - if (collapsingToolbarLayout != null) { - collapsingToolbarLayout!!.setContentScrimColor(primaryColor) - collapsingToolbarLayout!!.setStatusBarScrimColor(ColorUtil.darkenColor(primaryColor)) - } - - toolbar.setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp) setSupportActionBar(toolbar) - supportActionBar!!.title = null + val primaryColor = ThemeStore.primaryColor(this) + TintHelper.setTintAuto(contentContainer!!, primaryColor, true) + + if (collapsingToolbarLayout != null) { + collapsingToolbarLayout!!.apply { + setContentScrimColor(primaryColor) + setStatusBarScrimColor(ColorUtil.darkenColor(primaryColor)) + } + } + + + toolbar.setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp) if (toolbar != null && !PreferenceUtil.getInstance().fullScreenMode) { val params = toolbar!!.layoutParams as ViewGroup.MarginLayoutParams @@ -131,45 +138,29 @@ class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), AlbumDetailsContrac } if (appBarLayout != null) { - appBarLayout!!.addOnOffsetChangedListener(object : AppBarStateChangeListener() { - override fun onStateChanged(appBarLayout: AppBarLayout, state: AppBarStateChangeListener.State) { - val color: Int - when (state) { - AppBarStateChangeListener.State.COLLAPSED -> { - setLightStatusbar(ColorUtil.isColorLight(ThemeStore.primaryColor(this@AlbumDetailsActivity))) - color = ThemeStore.primaryColor(this@AlbumDetailsActivity) - } - AppBarStateChangeListener.State.EXPANDED, AppBarStateChangeListener.State.IDLE -> { - setLightStatusbar(false) - color = Color.TRANSPARENT - } - else -> { - setLightStatusbar(false) - color = Color.TRANSPARENT + appBarLayout!!.apply { + addOnOffsetChangedListener(object : AppBarStateChangeListener() { + override fun onStateChanged(appBarLayout: AppBarLayout, state: AppBarStateChangeListener.State) { + val color: Int = when (state) { + AppBarStateChangeListener.State.COLLAPSED -> { + setLightStatusbar(ColorUtil.isColorLight(ThemeStore.primaryColor(this@AlbumDetailsActivity))) + ThemeStore.primaryColor(this@AlbumDetailsActivity) + } + AppBarStateChangeListener.State.EXPANDED, AppBarStateChangeListener.State.IDLE -> { + setLightStatusbar(false) + Color.TRANSPARENT + } } + ToolbarContentTintHelper.setToolbarContentColorBasedOnToolbarColor(this@AlbumDetailsActivity, toolbar, color) } - ToolbarContentTintHelper.setToolbarContentColorBasedOnToolbarColor(this@AlbumDetailsActivity, toolbar, color) - } - }) - } - } - - @OnClick(R.id.action_shuffle_all, R.id.artist_image) - fun onViewClicked(view: View) { - when (view.id) { - R.id.artist_image -> { - val artistPairs = arrayOf>(Pair.create(image, resources.getString(R.string.transition_artist_image))) - NavigationUtil.goToArtist(this, album!!.artistId, *artistPairs) - } - R.id.action_shuffle_all -> if (album!!.songs != null) { - MusicPlayerRemote.openAndShuffleQueue(album!!.songs!!, true) + }) } } } override fun onPause() { super.onPause() - albumDetailsPresenter!!.unsubscribe() + albumDetailsPresenter.unsubscribe() } override fun loading() { @@ -196,7 +187,8 @@ class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), AlbumDetailsContrac loadAlbumCover() loadMoreFrom(album) - adapter!!.swapDataSet(album.songs) + + simpleSongAdapter.swapDataSet(album.songs) } private fun loadMoreFrom(album: Album) { @@ -213,20 +205,19 @@ class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), AlbumDetailsContrac }) } - val albums = ArtistLoader.getArtist(this, album.artistId) - .blockingFirst().albums + val albums = ArtistLoader.getArtist(this, album.artistId).blockingFirst().albums albums!!.remove(album) if (!albums.isEmpty()) { moreTitle.visibility = View.VISIBLE - moreRecyclerView!!.visibility = View.VISIBLE + moreRecyclerView.visibility = View.VISIBLE } else { return } moreTitle.text = String.format("More from %s", album.artistName) val albumAdapter = HorizontalAlbumAdapter(this, albums, false, null) - moreRecyclerView!!.layoutManager = GridLayoutManager(this, 1, GridLayoutManager.HORIZONTAL, false) - moreRecyclerView!!.adapter = albumAdapter + moreRecyclerView.layoutManager = GridLayoutManager(this, 1, GridLayoutManager.HORIZONTAL, false) + moreRecyclerView.adapter = albumAdapter } private fun loadAlbumCover() { @@ -262,7 +253,7 @@ class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), AlbumDetailsContrac private fun handleSortOrderMenuItem(item: MenuItem): Boolean { var sortOrder: String? = null - val songs = adapter!!.dataSet + val songs = simpleSongAdapter.dataSet when (item.itemId) { R.id.action_play_next -> { MusicPlayerRemote.playNext(songs) @@ -327,7 +318,7 @@ class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), AlbumDetailsContrac } private fun reload() { - albumDetailsPresenter!!.subscribe() + albumDetailsPresenter.subscribe() } companion object { diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/ArtistDetailActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/ArtistDetailActivity.java deleted file mode 100755 index 6bf6346e..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/ui/activities/ArtistDetailActivity.java +++ /dev/null @@ -1,458 +0,0 @@ -package code.name.monkey.retromusic.ui.activities; - -import android.content.Intent; -import android.content.res.ColorStateList; -import android.graphics.Color; -import android.os.Bundle; -import android.text.Html; -import android.text.Spanned; -import android.transition.Slide; -import android.view.Gravity; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewGroup.LayoutParams; -import android.view.animation.AnimationUtils; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; - -import com.bumptech.glide.Glide; -import com.google.android.material.appbar.AppBarLayout; -import com.google.android.material.appbar.CollapsingToolbarLayout; - -import java.util.ArrayList; -import java.util.Locale; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.widget.AppCompatTextView; -import androidx.appcompat.widget.Toolbar; -import androidx.core.app.ActivityCompat; -import androidx.core.widget.NestedScrollView; -import androidx.recyclerview.widget.DefaultItemAnimator; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import butterknife.BindView; -import butterknife.ButterKnife; -import butterknife.OnClick; -import code.name.monkey.appthemehelper.ThemeStore; -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.dialogs.AddToPlaylistDialog; -import code.name.monkey.retromusic.glide.ArtistGlideRequest; -import code.name.monkey.retromusic.glide.RetroMusicColoredTarget; -import code.name.monkey.retromusic.helper.MusicPlayerRemote; -import code.name.monkey.retromusic.misc.AppBarStateChangeListener; -import code.name.monkey.retromusic.model.Artist; -import code.name.monkey.retromusic.model.Song; -import code.name.monkey.retromusic.mvp.contract.ArtistDetailContract; -import code.name.monkey.retromusic.mvp.presenter.ArtistDetailsPresenter; -import code.name.monkey.retromusic.rest.LastFMRestClient; -import code.name.monkey.retromusic.rest.model.LastFmArtist; -import code.name.monkey.retromusic.ui.activities.base.AbsSlidingMusicPanelActivity; -import code.name.monkey.retromusic.ui.adapter.album.AlbumAdapter; -import code.name.monkey.retromusic.ui.adapter.album.HorizontalAlbumAdapter; -import code.name.monkey.retromusic.ui.adapter.song.SimpleSongAdapter; -import code.name.monkey.retromusic.util.CustomArtistImageUtil; -import code.name.monkey.retromusic.util.DensityUtil; -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.views.CollapsingFAB; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class ArtistDetailActivity extends AbsSlidingMusicPanelActivity implements - ArtistDetailContract.ArtistsDetailsView { - - public static final String EXTRA_ARTIST_ID = "extra_artist_id"; - private static final int REQUEST_CODE_SELECT_IMAGE = 9003; - @BindView(R.id.app_bar) - @Nullable - AppBarLayout appBarLayout; - - @BindView(R.id.collapsing_toolbar) - @Nullable - CollapsingToolbarLayout collapsingToolbarLayout; - - @BindView(R.id.image) - ImageView image; - - @BindView(R.id.biography) - TextView biographyTextView; - - @BindView(R.id.recycler_view) - RecyclerView recyclerView; - - @BindView(R.id.album_recycler_view) - RecyclerView albumRecyclerView; - - @BindView(R.id.album_title) - AppCompatTextView albumTitle; - - @BindView(R.id.song_title) - AppCompatTextView songTitle; - - @BindView(R.id.biography_title) - AppCompatTextView biographyTitle; - - @BindView(R.id.title) - TextView title; - - @BindView(R.id.text) - TextView text; - - @BindView(R.id.action_shuffle_all) - CollapsingFAB shuffleButton; - - @BindView(R.id.gradient_background) - @Nullable - View background; - - @BindView(R.id.image_container) - @Nullable - View imageContainer; - - @BindView(R.id.content) - NestedScrollView contentContainer; - - @BindView(R.id.toolbar) - Toolbar toolbar; - - @Nullable - private Spanned biography; - private Artist artist; - private LastFMRestClient lastFMRestClient; - private ArtistDetailsPresenter artistDetailsPresenter; - private SimpleSongAdapter songAdapter; - private AlbumAdapter albumAdapter; - private boolean forceDownload; - - void setupWindowTransistion() { - Slide slide = new Slide(Gravity.BOTTOM); - slide.setInterpolator( - AnimationUtils.loadInterpolator(this, android.R.interpolator.linear_out_slow_in)); - getWindow().setEnterTransition(slide); - - } - - - @Override - protected View createContentView() { - return wrapSlidingMusicPanel(R.layout.activity_artist_details); - } - - @Override - protected void onCreate(Bundle bundle) { - setDrawUnderStatusBar(); - setupWindowTransistion(); - super.onCreate(bundle); - ButterKnife.bind(this); - - toggleBottomNavigationView(true); - setNavigationbarColorAuto(); - setLightNavigationBar(true); - - ActivityCompat.postponeEnterTransition(this); - - lastFMRestClient = new LastFMRestClient(this); - - setUpViews(); - - artistDetailsPresenter = new ArtistDetailsPresenter(this, getIntent().getExtras()); - artistDetailsPresenter.subscribe(); - - contentContainer.setOnScrollChangeListener((NestedScrollView.OnScrollChangeListener) (v, scrollX, scrollY, oldScrollX, oldScrollY) -> { - if (scrollY > oldScrollY) { - shuffleButton.setShowTitle(false); - } - if (scrollY < oldScrollY) { - shuffleButton.setShowTitle(true); - } - }); - } - - private void setUpViews() { - setupRecyclerView(); - setupToolbarMarginHeight(); - setupContainerHeight(); - } - - private void setupContainerHeight() { - if (imageContainer != null) { - LayoutParams params = imageContainer.getLayoutParams(); - params.width = DensityUtil.getScreenHeight(this) / 2; - imageContainer.setLayoutParams(params); - } - } - - private void setupToolbarMarginHeight() { - int primaryColor = ThemeStore.primaryColor(this); - TintHelper.setTintAuto(contentContainer, primaryColor, true); - if (collapsingToolbarLayout != null) { - collapsingToolbarLayout.setContentScrimColor(primaryColor); - collapsingToolbarLayout.setStatusBarScrimColor(ColorUtil.darkenColor(primaryColor)); - } - - toolbar.setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp); - setSupportActionBar(toolbar); - //noinspection ConstantConditions - getSupportActionBar().setTitle(null); - - - if (toolbar != null && !PreferenceUtil.getInstance().getFullScreenMode()) { - ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) toolbar.getLayoutParams(); - params.topMargin = RetroUtil.getStatusBarHeight( ); - toolbar.setLayoutParams(params); - } - - if (appBarLayout != null) { - appBarLayout.addOnOffsetChangedListener(new AppBarStateChangeListener() { - @Override - public void onStateChanged(AppBarLayout appBarLayout, State state) { - int color; - switch (state) { - case COLLAPSED: - setLightStatusbar(ColorUtil.isColorLight(ThemeStore.primaryColor(appBarLayout.getContext()))); - color = ThemeStore.primaryColor(appBarLayout.getContext()); - break; - default: - case EXPANDED: - case IDLE: - setLightStatusbar(false); - color = Color.TRANSPARENT; - break; - } - ToolbarContentTintHelper.setToolbarContentColorBasedOnToolbarColor(appBarLayout.getContext(), toolbar, color); - } - }); - } - } - - private void setupRecyclerView() { - albumAdapter = new HorizontalAlbumAdapter(this, new ArrayList<>(), false, null); - albumRecyclerView.setItemAnimator(new DefaultItemAnimator()); - albumRecyclerView.setLayoutManager(new GridLayoutManager(this, 1, GridLayoutManager.HORIZONTAL, false)); - albumRecyclerView.setAdapter(albumAdapter); - - songAdapter = new SimpleSongAdapter(this, new ArrayList<>(), R.layout.item_song); - recyclerView.setItemAnimator(new DefaultItemAnimator()); - recyclerView.setLayoutManager(new LinearLayoutManager(this)); - recyclerView.setAdapter(songAdapter); - } - - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - switch (requestCode) { - case REQUEST_CODE_SELECT_IMAGE: - if (resultCode == RESULT_OK) { - CustomArtistImageUtil.getInstance(this).setCustomArtistImage(artist, data.getData()); - } - break; - default: - if (resultCode == RESULT_OK) { - reload(); - } - break; - } - } - - @Override - protected void onPause() { - super.onPause(); - artistDetailsPresenter.unsubscribe(); - } - - @Override - public void loading() { - } - - @Override - public void showEmptyView() { - - } - - @Override - public void completed() { - ActivityCompat.startPostponedEnterTransition(this); - } - - @Override - public void showData(Artist artist) { - setArtist(artist); - } - - private Artist getArtist() { - if (artist == null) { - artist = new Artist(); - } - return artist; - } - - private void setArtist(Artist artist) { - if (artist.getSongCount() <= 0) { - finish(); - } - this.artist = artist; - loadArtistImage(); - - if (RetroUtil.isAllowedToDownloadMetadata(this)) { - loadBiography(); - } - title.setText(artist.getName()); - text.setText(String.format("%s • %s", MusicUtil.getArtistInfoString(this, artist), MusicUtil - .getReadableDurationString(MusicUtil.getTotalDuration(this, artist.getSongs())))); - - songAdapter.swapDataSet(artist.getSongs()); - albumAdapter.swapDataSet(artist.getAlbums()); - } - - private void loadBiography() { - loadBiography(Locale.getDefault().getLanguage()); - } - - private void loadBiography(@Nullable final String lang) { - biography = null; - - lastFMRestClient.getApiService() - .getArtistInfo(getArtist().getName(), lang, null) - .enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, - @NonNull Response response) { - final LastFmArtist lastFmArtist = response.body(); - if (lastFmArtist != null && lastFmArtist.getArtist() != null) { - final String bioContent = lastFmArtist.getArtist().getBio().getContent(); - if (bioContent != null && !bioContent.trim().isEmpty()) { - //TransitionManager.beginDelayedTransition(titleContainer); - biographyTextView.setVisibility(View.VISIBLE); - biographyTitle.setVisibility(View.VISIBLE); - biography = Html.fromHtml(bioContent); - biographyTextView.setText(biography); - } - } - - // If the "lang" parameter is set and no biography is given, retry with default language - if (biography == null && lang != null) { - loadBiography(null); - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - t.printStackTrace(); - biography = null; - } - }); - } - - @OnClick(R.id.biography) - void toggleArtistBiography() { - if (biographyTextView.getMaxLines() == 4) { - biographyTextView.setMaxLines(Integer.MAX_VALUE); - } else { - biographyTextView.setMaxLines(4); - } - } - - private void loadArtistImage() { - ArtistGlideRequest.Builder.from(Glide.with(this), artist) - .forceDownload(forceDownload) - .generatePalette(this).build() - .dontAnimate() - .into(new RetroMusicColoredTarget(image) { - @Override - public void onColorReady(int color) { - setColors(color); - } - }); - forceDownload = false; - } - - private void setColors(int color) { - - int textColor = PreferenceUtil.getInstance().getAdaptiveColor() ? color : ThemeStore.accentColor(this); - - albumTitle.setTextColor(textColor); - songTitle.setTextColor(textColor); - biographyTitle.setTextColor(textColor); - - shuffleButton.setColor(textColor); - - if (background != null) { - background.setBackgroundTintList(ColorStateList.valueOf(color)); - } - findViewById(R.id.root).setBackgroundColor(ThemeStore.primaryColor(this)); - } - - @OnClick({R.id.action_shuffle_all}) - public void onViewClicked(View view) { - switch (view.getId()) { - case R.id.action_shuffle_all: - MusicPlayerRemote.INSTANCE.openAndShuffleQueue(getArtist().getSongs(), true); - break; - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - return handleSortOrderMenuItem(item); - } - - private boolean handleSortOrderMenuItem(@NonNull MenuItem item) { - final ArrayList songs = getArtist().getSongs(); - switch (item.getItemId()) { - case android.R.id.home: - super.onBackPressed(); - return true; - case R.id.action_play_next: - MusicPlayerRemote.INSTANCE.playNext(songs); - return true; - case R.id.action_add_to_current_playing: - MusicPlayerRemote.INSTANCE.enqueue(songs); - return true; - case R.id.action_add_to_playlist: - AddToPlaylistDialog.create(songs).show(getSupportFragmentManager(), "ADD_PLAYLIST"); - return true; - case R.id.action_set_artist_image: - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType("image/*"); - startActivityForResult( - Intent.createChooser(intent, getString(R.string.pick_from_local_storage)), - REQUEST_CODE_SELECT_IMAGE); - return true; - case R.id.action_reset_artist_image: - Toast.makeText(ArtistDetailActivity.this, getResources().getString(R.string.updating), - Toast.LENGTH_SHORT).show(); - CustomArtistImageUtil.getInstance(ArtistDetailActivity.this).resetCustomArtistImage(artist); - forceDownload = true; - return true; - } - return true; - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.menu_artist_detail, menu); - return super.onCreateOptionsMenu(menu); - } - - @Override - public void onMediaStoreChanged() { - super.onMediaStoreChanged(); - reload(); - } - - private void reload() { - artistDetailsPresenter.unsubscribe(); - artistDetailsPresenter.subscribe(); - } -} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/ArtistDetailActivity.kt b/app/src/main/java/code/name/monkey/retromusic/ui/activities/ArtistDetailActivity.kt new file mode 100755 index 00000000..c47a6074 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/ArtistDetailActivity.kt @@ -0,0 +1,363 @@ +package code.name.monkey.retromusic.ui.activities + +import android.app.Activity +import android.content.Intent +import android.graphics.Color +import android.os.Build +import android.os.Bundle +import android.text.Html +import android.text.Spanned +import android.transition.Slide +import android.view.* +import android.view.animation.AnimationUtils +import android.widget.Toast +import androidx.core.app.ActivityCompat +import androidx.core.widget.NestedScrollView +import androidx.recyclerview.widget.DefaultItemAnimator +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager +import butterknife.ButterKnife +import code.name.monkey.appthemehelper.ThemeStore +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.dialogs.AddToPlaylistDialog +import code.name.monkey.retromusic.glide.ArtistGlideRequest +import code.name.monkey.retromusic.glide.RetroMusicColoredTarget +import code.name.monkey.retromusic.helper.MusicPlayerRemote +import code.name.monkey.retromusic.misc.AppBarStateChangeListener +import code.name.monkey.retromusic.model.Artist +import code.name.monkey.retromusic.mvp.contract.ArtistDetailContract +import code.name.monkey.retromusic.mvp.presenter.ArtistDetailsPresenter +import code.name.monkey.retromusic.rest.LastFMRestClient +import code.name.monkey.retromusic.rest.model.LastFmArtist +import code.name.monkey.retromusic.ui.activities.base.AbsSlidingMusicPanelActivity +import code.name.monkey.retromusic.ui.adapter.album.AlbumAdapter +import code.name.monkey.retromusic.ui.adapter.album.HorizontalAlbumAdapter +import code.name.monkey.retromusic.ui.adapter.song.SimpleSongAdapter +import code.name.monkey.retromusic.util.* +import com.bumptech.glide.Glide +import com.google.android.material.appbar.AppBarLayout +import kotlinx.android.synthetic.main.activity_artist_content.* +import kotlinx.android.synthetic.main.activity_artist_details.* +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import java.util.* + +class ArtistDetailActivity : AbsSlidingMusicPanelActivity(), ArtistDetailContract.ArtistsDetailsView { + + private var biography: Spanned? = null + private var artist: Artist? = null + private var lastFMRestClient: LastFMRestClient? = null + private var artistDetailsPresenter: ArtistDetailsPresenter? = null + private var songAdapter: SimpleSongAdapter? = null + private var albumAdapter: AlbumAdapter? = null + private var forceDownload: Boolean = false + + private fun setupWindowTransistion() { + val slide = Slide(Gravity.BOTTOM) + slide.interpolator = AnimationUtils.loadInterpolator(this, android.R.interpolator.linear_out_slow_in) + window.enterTransition = slide + } + + + override fun createContentView(): View { + return wrapSlidingMusicPanel(R.layout.activity_artist_details) + } + + override fun onCreate(savedInstanceState: Bundle?) { + setDrawUnderStatusBar() + setupWindowTransistion() + super.onCreate(savedInstanceState) + ButterKnife.bind(this) + + toggleBottomNavigationView(true) + setNavigationbarColorAuto() + setLightNavigationBar(true) + + ActivityCompat.postponeEnterTransition(this) + + lastFMRestClient = LastFMRestClient(this) + + setUpViews() + + artistDetailsPresenter = ArtistDetailsPresenter(this, intent.extras) + artistDetailsPresenter!!.subscribe() + + contentContainer.setOnScrollChangeListener { _: NestedScrollView?, _: Int, scrollY: Int, _: Int, oldScrollY: Int -> + run { + if (scrollY > oldScrollY) { + actionShuffleAll!!.setShowTitle(false) + } + if (scrollY < oldScrollY) { + actionShuffleAll!!.setShowTitle(true) + } + } + } + + biographyText.setOnClickListener { + if (biographyText.maxLines == 4) { + biographyText.maxLines = Integer.MAX_VALUE + } else { + biographyText.maxLines = 4 + } + } + actionShuffleAll.setOnClickListener { MusicPlayerRemote.openAndShuffleQueue(getArtist().songs, true) } + } + + private fun setUpViews() { + setupRecyclerView() + setupToolbarMarginHeight() + setupContainerHeight() + } + + private fun setupContainerHeight() { + if (imageContainer != null) { + val params = imageContainer!!.layoutParams + params.width = DensityUtil.getScreenHeight(this) / 2 + imageContainer!!.layoutParams = params + } + } + + private fun setupToolbarMarginHeight() { + val primaryColor = ThemeStore.primaryColor(this) + TintHelper.setTintAuto(contentContainer!!, primaryColor, true) + if (collapsingToolbar != null) { + collapsingToolbar!!.setContentScrimColor(primaryColor) + collapsingToolbar!!.setStatusBarScrimColor(ColorUtil.darkenColor(primaryColor)) + } + + toolbar!!.setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp) + setSupportActionBar(toolbar) + + supportActionBar!!.title = null + + + if (toolbar != null && !PreferenceUtil.getInstance().fullScreenMode) { + val params = toolbar!!.layoutParams as ViewGroup.MarginLayoutParams + params.topMargin = RetroUtil.getStatusBarHeight() + toolbar!!.layoutParams = params + } + + if (appBarLayout != null) { + appBarLayout!!.addOnOffsetChangedListener(object : AppBarStateChangeListener() { + override fun onStateChanged(appBarLayout: AppBarLayout, state: AppBarStateChangeListener.State) { + val color: Int + when (state) { + AppBarStateChangeListener.State.COLLAPSED -> { + setLightStatusbar(ColorUtil.isColorLight(ThemeStore.primaryColor(appBarLayout.context))) + color = ThemeStore.primaryColor(appBarLayout.context) + } + AppBarStateChangeListener.State.EXPANDED, AppBarStateChangeListener.State.IDLE -> { + setLightStatusbar(false) + color = Color.TRANSPARENT + } + + } + ToolbarContentTintHelper.setToolbarContentColorBasedOnToolbarColor(appBarLayout.context, toolbar, color) + } + }) + } + } + + private fun setupRecyclerView() { + albumAdapter = HorizontalAlbumAdapter(this, ArrayList(), false, null) + albumRecyclerView.apply { + itemAnimator = DefaultItemAnimator() + layoutManager = GridLayoutManager(this.context, 1, GridLayoutManager.HORIZONTAL, false) + adapter = albumAdapter + } + songAdapter = SimpleSongAdapter(this, ArrayList(), R.layout.item_song) + recyclerView.apply { + itemAnimator = DefaultItemAnimator() + layoutManager = LinearLayoutManager(this.context) + adapter = songAdapter + } + } + + + 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) { + CustomArtistImageUtil.getInstance(this).setCustomArtistImage(artist, data!!.data) + } + else -> if (resultCode == Activity.RESULT_OK) { + reload() + } + } + } + + override fun onPause() { + super.onPause() + artistDetailsPresenter!!.unsubscribe() + } + + override fun loading() {} + + override fun showEmptyView() { + + } + + override fun completed() { + ActivityCompat.startPostponedEnterTransition(this) + } + + override fun showData(artist: Artist) { + setArtist(artist) + } + + private fun getArtist(): Artist { + if (artist == null) { + artist = Artist() + } + return this.artist!! + } + + private fun setArtist(artist: Artist) { + if (artist.songCount <= 0) { + finish() + } + this.artist = artist + loadArtistImage() + + if (RetroUtil.isAllowedToDownloadMetadata(this)) { + loadBiography() + } + artistTitle.text = artist.name + text.text = String.format("%s • %s", MusicUtil.getArtistInfoString(this, artist), MusicUtil + .getReadableDurationString(MusicUtil.getTotalDuration(this, artist.songs))) + + songAdapter!!.swapDataSet(artist.songs) + albumAdapter!!.swapDataSet(artist.albums!!) + } + + private fun loadBiography(lang: String? = Locale.getDefault().language) { + biography = null + + lastFMRestClient!!.apiService + .getArtistInfo(getArtist().name, lang, null) + .enqueue(object : Callback { + override fun onResponse(call: Call, + response: Response) { + val lastFmArtist = response.body() + if (lastFmArtist != null && lastFmArtist.artist != null) { + val bioContent = lastFmArtist.artist.bio.content + if (bioContent != null && !bioContent.trim { it <= ' ' }.isEmpty()) { + //TransitionManager.beginDelayedTransition(titleContainer); + biographyText.visibility = View.VISIBLE + biographyTitle.visibility = View.VISIBLE + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + biography = Html.fromHtml(bioContent, Html.FROM_HTML_MODE_LEGACY) + } else { + biography = Html.fromHtml(bioContent) + } + biographyText!!.text = biography + } + } + + // If the "lang" parameter is set and no biography is given, retry with default language + if (biography == null && lang != null) { + loadBiography(null) + } + } + + override fun onFailure(call: Call, t: Throwable) { + t.printStackTrace() + biography = null + } + }) + } + + + private fun loadArtistImage() { + ArtistGlideRequest.Builder.from(Glide.with(this), artist) + .forceDownload(forceDownload) + .generatePalette(this).build() + .dontAnimate() + .into(object : RetroMusicColoredTarget(artistImage) { + override fun onColorReady(color: Int) { + setColors(color) + } + }) + forceDownload = false + } + + private fun setColors(color: Int) { + + val textColor = if (PreferenceUtil.getInstance().adaptiveColor) color else ThemeStore.accentColor(this) + + albumTitle.setTextColor(textColor) + songTitle.setTextColor(textColor) + biographyTitle.setTextColor(textColor) + + actionShuffleAll.setColor(textColor) + + + findViewById(R.id.root).setBackgroundColor(ThemeStore.primaryColor(this)) + } + + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return handleSortOrderMenuItem(item) + } + + private fun handleSortOrderMenuItem(item: MenuItem): Boolean { + val songs = getArtist().songs + when (item.itemId) { + android.R.id.home -> { + super.onBackPressed() + return true + } + 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 -> { + AddToPlaylistDialog.create(songs).show(supportFragmentManager, "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 -> { + Toast.makeText(this@ArtistDetailActivity, resources.getString(R.string.updating), + Toast.LENGTH_SHORT).show() + CustomArtistImageUtil.getInstance(this@ArtistDetailActivity).resetCustomArtistImage(artist) + forceDownload = true + return true + } + } + return true + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_artist_detail, menu) + return super.onCreateOptionsMenu(menu) + } + + override fun onMediaStoreChanged() { + super.onMediaStoreChanged() + reload() + } + + private fun reload() { + artistDetailsPresenter!!.unsubscribe() + artistDetailsPresenter!!.subscribe() + } + + companion object { + + const val EXTRA_ARTIST_ID = "extra_artist_id" + const val REQUEST_CODE_SELECT_IMAGE = 9003 + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/ErrorHandlerActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/ErrorHandlerActivity.java index 5a0ecd4c..10253ff9 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/activities/ErrorHandlerActivity.java +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/ErrorHandlerActivity.java @@ -1 +1 @@ -package code.name.monkey.retromusic.ui.activities; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import android.view.View; import butterknife.OnClick; import code.name.monkey.retromusic.R; import code.name.monkey.retromusic.RetroApplication; public class ErrorHandlerActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_error_handler); } @OnClick(R.id.clear_app_data) void clearAppDate(View view) { RetroApplication.Companion.deleteAppData(); } } \ No newline at end of file +package code.name.monkey.retromusic.ui.activities; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import android.view.View; import butterknife.OnClick; import code.name.monkey.retromusic.R; import code.name.monkey.retromusic.App; public class ErrorHandlerActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_error_handler); } @OnClick(R.id.clear_app_data) void clearAppDate(View view) { App.Companion.deleteAppData(); } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/GenreDetailsActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/GenreDetailsActivity.java deleted file mode 100644 index f7130704..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/ui/activities/GenreDetailsActivity.java +++ /dev/null @@ -1,226 +0,0 @@ -package code.name.monkey.retromusic.ui.activities; - -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.ProgressBar; -import android.widget.TextView; - -import com.afollestad.materialcab.MaterialCab; -import com.google.android.material.appbar.AppBarLayout; -import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView; - -import java.util.ArrayList; - -import androidx.annotation.NonNull; -import androidx.appcompat.widget.Toolbar; -import androidx.recyclerview.widget.DefaultItemAnimator; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import butterknife.BindView; -import butterknife.ButterKnife; -import butterknife.OnClick; -import code.name.monkey.appthemehelper.ThemeStore; -import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; -import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.helper.MusicPlayerRemote; -import code.name.monkey.retromusic.helper.menu.GenreMenuHelper; -import code.name.monkey.retromusic.interfaces.CabHolder; -import code.name.monkey.retromusic.model.Genre; -import code.name.monkey.retromusic.model.Song; -import code.name.monkey.retromusic.mvp.contract.GenreDetailsContract; -import code.name.monkey.retromusic.mvp.presenter.GenreDetailsPresenter; -import code.name.monkey.retromusic.ui.activities.base.AbsSlidingMusicPanelActivity; -import code.name.monkey.retromusic.ui.adapter.song.SongAdapter; -import code.name.monkey.retromusic.util.RetroColorUtil; -import code.name.monkey.retromusic.util.ViewUtil; -import code.name.monkey.retromusic.views.CollapsingFAB; - -/** - * @author Hemanth S (h4h13). - */ - -public class GenreDetailsActivity extends AbsSlidingMusicPanelActivity implements GenreDetailsContract.GenreDetailsView, CabHolder { - public static final String EXTRA_GENRE_ID = "extra_genre_id"; - - @BindView(R.id.recycler_view) - RecyclerView recyclerView; - - @BindView(R.id.toolbar) - Toolbar toolbar; - - @BindView(android.R.id.empty) - TextView empty; - - @BindView(R.id.action_shuffle) - CollapsingFAB shuffleButton; - - @BindView(R.id.progress_bar) - ProgressBar progressBar; - - @BindView(R.id.app_bar) - AppBarLayout appBarLayout; - - @BindView(R.id.title) - TextView title; - - private Genre genre; - private GenreDetailsPresenter presenter; - private SongAdapter songAdapter; - private MaterialCab cab; - - private void checkIsEmpty() { - empty.setVisibility( - songAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE - ); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - setDrawUnderStatusBar(); - super.onCreate(savedInstanceState); - ButterKnife.bind(this); - - setNavigationbarColorAuto(); - setTaskDescriptionColorAuto(); - toggleBottomNavigationView(true); - setLightNavigationBar(true); - - - genre = getIntent().getParcelableExtra(EXTRA_GENRE_ID); - presenter = new GenreDetailsPresenter(this, genre.getId()); - - setUpToolBar(); - setupRecyclerView(); - } - - @OnClick({R.id.action_shuffle}) - public void onViewClicked(View view) { - switch (view.getId()) { - case R.id.action_shuffle: - MusicPlayerRemote.INSTANCE.openAndShuffleQueue(songAdapter.getDataSet(), true); - break; - } - } - - private void setUpToolBar() { - title.setText(genre.getName()); - title.setTextColor(ThemeStore.textColorPrimary(this)); - - int primaryColor = ThemeStore.primaryColor(this); - toolbar.setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp); - toolbar.setBackgroundColor(primaryColor); - appBarLayout.setBackgroundColor(primaryColor); - ToolbarContentTintHelper.colorBackButton(toolbar, ThemeStore.accentColor(this)); - setTitle(null); - setSupportActionBar(toolbar); - shuffleButton.setColor(ThemeStore.accentColor(this)); - } - - @Override - protected void onResume() { - super.onResume(); - presenter.subscribe(); - } - - @Override - protected void onPause() { - super.onPause(); - presenter.unsubscribe(); - } - - @Override - protected View createContentView() { - return wrapSlidingMusicPanel(R.layout.activity_playlist_detail); - } - - - @Override - public void loading() { - - } - - @Override - public void showEmptyView() { - - } - - @Override - public void completed() { - - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.menu_genre_detail, menu); - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - onBackPressed(); - } - return GenreMenuHelper.INSTANCE.handleMenuClick(this, genre, item); - } - - private void setupRecyclerView() { - ViewUtil.setUpFastScrollRecyclerViewColor(this, - ((FastScrollRecyclerView) recyclerView), ThemeStore.accentColor(this)); - songAdapter = new SongAdapter(this, new ArrayList<>(), R.layout.item_list, false, this); - recyclerView.setItemAnimator(new DefaultItemAnimator()); - recyclerView.setLayoutManager(new LinearLayoutManager(this)); - recyclerView.setAdapter(songAdapter); - recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { - @Override - public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { - super.onScrolled(recyclerView, dx, dy); - if (dy > 0) { - shuffleButton.setShowTitle(false); - } else if (dy < 0) { - shuffleButton.setShowTitle(true); - } - } - }); - songAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { - @Override - public void onChanged() { - super.onChanged(); - checkIsEmpty(); - } - }); - } - - @Override - public void showData(ArrayList songs) { - songAdapter.swapDataSet(songs); - } - - @NonNull - @Override - public MaterialCab openCab(final int menu, final MaterialCab.Callback callback) { - if (cab != null && cab.isActive()) cab.finish(); - cab = new MaterialCab(this, R.id.cab_stub) - .setMenu(menu) - .setCloseDrawableRes(R.drawable.ic_close_white_24dp) - .setBackgroundColor(RetroColorUtil.shiftBackgroundColorForLightText(ThemeStore.primaryColor(this))) - .start(callback); - return cab; - } - - @Override - public void onBackPressed() { - if (cab != null && cab.isActive()) cab.finish(); - else { - recyclerView.stopScroll(); - super.onBackPressed(); - } - } - - @Override - public void onMediaStoreChanged() { - super.onMediaStoreChanged(); - presenter.subscribe(); - } -} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/GenreDetailsActivity.kt b/app/src/main/java/code/name/monkey/retromusic/ui/activities/GenreDetailsActivity.kt new file mode 100644 index 00000000..b1178cc1 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/GenreDetailsActivity.kt @@ -0,0 +1,174 @@ +package code.name.monkey.retromusic.ui.activities + +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.View +import androidx.recyclerview.widget.DefaultItemAnimator +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import butterknife.ButterKnife +import code.name.monkey.appthemehelper.ThemeStore +import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper +import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.helper.MusicPlayerRemote +import code.name.monkey.retromusic.helper.menu.GenreMenuHelper +import code.name.monkey.retromusic.interfaces.CabHolder +import code.name.monkey.retromusic.model.Genre +import code.name.monkey.retromusic.model.Song +import code.name.monkey.retromusic.mvp.contract.GenreDetailsContract +import code.name.monkey.retromusic.mvp.presenter.GenreDetailsPresenter +import code.name.monkey.retromusic.ui.activities.base.AbsSlidingMusicPanelActivity +import code.name.monkey.retromusic.ui.adapter.song.SongAdapter +import code.name.monkey.retromusic.util.RetroColorUtil +import code.name.monkey.retromusic.util.ViewUtil +import com.afollestad.materialcab.MaterialCab +import kotlinx.android.synthetic.main.activity_playlist_detail.* +import java.util.* + +/** + * @author Hemanth S (h4h13). + */ + +class GenreDetailsActivity : AbsSlidingMusicPanelActivity(), GenreDetailsContract.GenreDetailsView, CabHolder { + + private var genre: Genre? = null + private var presenter: GenreDetailsPresenter? = null + private var songAdapter: SongAdapter? = null + private var cab: MaterialCab? = null + + private fun checkIsEmpty() { + empty!!.visibility = if (songAdapter!!.itemCount == 0) View.VISIBLE else View.GONE + } + + override fun onCreate(savedInstanceState: Bundle?) { + setDrawUnderStatusBar() + super.onCreate(savedInstanceState) + ButterKnife.bind(this) + + setStatusbarColorAuto() + setNavigationbarColorAuto() + setTaskDescriptionColorAuto() + + toggleBottomNavigationView(true) + setLightNavigationBar(true) + + + genre = intent.extras!!.getParcelable(EXTRA_GENRE_ID) + presenter = GenreDetailsPresenter(this, genre!!.id) + + setUpToolBar() + setupRecyclerView() + actionShuffle.setOnClickListener { MusicPlayerRemote.openAndShuffleQueue(songAdapter!!.dataSet, true) } + } + + private fun setUpToolBar() { + bannerTitle!!.text = genre!!.name + bannerTitle!!.setTextColor(ThemeStore.textColorPrimary(this)) + + val primaryColor = ThemeStore.primaryColor(this) + toolbar.setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp) + toolbar.setBackgroundColor(primaryColor) + appBarLayout.setBackgroundColor(primaryColor) + ToolbarContentTintHelper.colorBackButton(toolbar!!, ThemeStore.accentColor(this)) + title = null + setSupportActionBar(toolbar) + actionShuffle.setColor(ThemeStore.accentColor(this)) + } + + override fun onResume() { + super.onResume() + presenter!!.subscribe() + } + + override fun onPause() { + super.onPause() + presenter!!.unsubscribe() + } + + override fun createContentView(): View { + return wrapSlidingMusicPanel(R.layout.activity_playlist_detail) + } + + + override fun loading() { + + } + + override fun showEmptyView() { + + } + + override fun completed() { + + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_genre_detail, menu) + return super.onCreateOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == android.R.id.home) { + onBackPressed() + } + return GenreMenuHelper.handleMenuClick(this, genre!!, item) + } + + private fun setupRecyclerView() { + ViewUtil.setUpFastScrollRecyclerViewColor(this, recyclerView, ThemeStore.accentColor(this)) + songAdapter = SongAdapter(this, ArrayList(), R.layout.item_list, false, this) + recyclerView.apply { + itemAnimator = DefaultItemAnimator() + layoutManager = LinearLayoutManager(this@GenreDetailsActivity) + adapter = songAdapter + }.addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + if (dy > 0) { + actionShuffle.setShowTitle(false) + } else if (dy < 0) { + actionShuffle.setShowTitle(true) + } + } + }) + songAdapter!!.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { + override fun onChanged() { + super.onChanged() + checkIsEmpty() + } + }) + } + + override fun showData(songs: ArrayList) { + songAdapter!!.swapDataSet(songs) + } + + override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab { + if (cab != null && cab!!.isActive) cab!!.finish() + cab = MaterialCab(this, R.id.cab_stub) + .setMenu(menuRes) + .setCloseDrawableRes(R.drawable.ic_close_white_24dp) + .setBackgroundColor(RetroColorUtil.shiftBackgroundColorForLightText(ThemeStore.primaryColor(this))) + .start(callback) + return cab!! + } + + override fun onBackPressed() { + if (cab != null && cab!!.isActive) + cab!!.finish() + else { + recyclerView!!.stopScroll() + super.onBackPressed() + } + } + + override fun onMediaStoreChanged() { + super.onMediaStoreChanged() + presenter!!.subscribe() + } + + companion object { + const val EXTRA_GENRE_ID = "extra_genre_id" + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/MainActivity.kt b/app/src/main/java/code/name/monkey/retromusic/ui/activities/MainActivity.kt index e875ca79..81f934b6 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/activities/MainActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/MainActivity.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.content.* import android.content.pm.PackageManager import android.os.Bundle +import android.os.Handler import android.preference.PreferenceManager import android.provider.MediaStore import android.util.Log @@ -12,8 +13,11 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import butterknife.ButterKnife +import code.name.monkey.appthemehelper.ThemeStore +import code.name.monkey.appthemehelper.util.ATHUtil +import code.name.monkey.appthemehelper.util.NavigationViewUtil +import code.name.monkey.retromusic.App import code.name.monkey.retromusic.R -import code.name.monkey.retromusic.RetroApplication import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.SearchQueryHelper import code.name.monkey.retromusic.interfaces.MainActivityFragmentCallbacks @@ -23,16 +27,18 @@ import code.name.monkey.retromusic.loaders.PlaylistSongsLoader import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.ui.activities.base.AbsSlidingMusicPanelActivity import code.name.monkey.retromusic.ui.fragments.mainactivity.LibraryFragment +import code.name.monkey.retromusic.ui.fragments.mainactivity.folders.FoldersFragment import code.name.monkey.retromusic.ui.fragments.mainactivity.home.BannerHomeFragment import code.name.monkey.retromusic.util.PreferenceUtil import com.afollestad.materialdialogs.MaterialDialog -import com.google.android.material.bottomnavigation.BottomNavigationView import io.reactivex.disposables.CompositeDisposable +import kotlinx.android.synthetic.main.activity_main_drawer_layout.* import java.util.* -class MainActivity : AbsSlidingMusicPanelActivity(), SharedPreferences.OnSharedPreferenceChangeListener, BottomNavigationView.OnNavigationItemSelectedListener { - lateinit var currentFragment: MainActivityFragmentCallbacks +class MainActivity : AbsSlidingMusicPanelActivity(), SharedPreferences.OnSharedPreferenceChangeListener { + + private lateinit var currentFragment: MainActivityFragmentCallbacks private var blockRequestPermissions: Boolean = false private val disposable = CompositeDisposable() @@ -63,16 +69,23 @@ class MainActivity : AbsSlidingMusicPanelActivity(), SharedPreferences.OnSharedP super.onCreate(savedInstanceState) ButterKnife.bind(this) - getBottomNavigationView()!!.setOnNavigationItemSelectedListener(this) + getBottomNavigationView()!!.setOnNavigationItemSelectedListener { + PreferenceUtil.getInstance().lastPage = it.itemId + selectedFragment(it.itemId) + true + } + + setUpDrawerLayout() if (savedInstanceState == null) { - selectedFragment(PreferenceUtil.getInstance().lastPage) + setMusicChooser(PreferenceUtil.getInstance().lastMusicChooser) } else { - restoreCurrentFragment() + restoreCurrentFragment(); } + checkShowChangelog() - if (!RetroApplication.isProVersion && !PreferenceManager.getDefaultSharedPreferences(this).getBoolean("shown", false)) { + if (!App.isProVersion && !PreferenceManager.getDefaultSharedPreferences(this).getBoolean("shown", false)) { showPromotionalOffer() } } @@ -104,7 +117,6 @@ class MainActivity : AbsSlidingMusicPanelActivity(), SharedPreferences.OnSharedP intent.putExtra("expand", false) } } - } override fun onDestroy() { @@ -114,16 +126,12 @@ class MainActivity : AbsSlidingMusicPanelActivity(), SharedPreferences.OnSharedP PreferenceUtil.getInstance().unregisterOnSharedPreferenceChangedListener(this) } - fun setCurrentFragment(fragment: Fragment, isStackAdd: Boolean, tag: String) { - val fragmentTransaction = supportFragmentManager.beginTransaction() - fragmentTransaction.replace(R.id.fragment_container, fragment, tag) - if (isStackAdd) { - fragmentTransaction.addToBackStack(tag) - } - fragmentTransaction.commit() + private fun setCurrentFragment(fragment: Fragment) { + supportFragmentManager.beginTransaction().replace(R.id.fragment_container, fragment, null).commit() currentFragment = fragment as MainActivityFragmentCallbacks } + private fun restoreCurrentFragment() { currentFragment = supportFragmentManager.findFragmentById(R.id.fragment_container) as MainActivityFragmentCallbacks } @@ -139,7 +147,6 @@ class MainActivity : AbsSlidingMusicPanelActivity(), SharedPreferences.OnSharedP if (intent.action != null && intent.action == MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH) { val songs = SearchQueryHelper.getSongs(this, intent.extras!!) - if (MusicPlayerRemote.shuffleMode == MusicService.SHUFFLE_MODE_SHUFFLE) { MusicPlayerRemote.openAndShuffleQueue(songs, true) } else { @@ -148,15 +155,14 @@ class MainActivity : AbsSlidingMusicPanelActivity(), SharedPreferences.OnSharedP handled = true } - if (uri != null && uri.toString().length > 0) { + if (uri != null && uri.toString().isNotEmpty()) { MusicPlayerRemote.playFromUri(uri) handled = true } else if (MediaStore.Audio.Playlists.CONTENT_TYPE == mimeType) { val id = parseIdFromIntent(intent, "playlistId", "playlist").toInt() if (id >= 0) { val position = intent.getIntExtra("position", 0) - val songs = ArrayList( - PlaylistSongsLoader.getPlaylistSongList(this, id).blockingFirst()) + val songs = ArrayList(PlaylistSongsLoader.getPlaylistSongList(this, id).blockingFirst()) MusicPlayerRemote.openQueue(songs, position, true) handled = true } @@ -164,16 +170,14 @@ class MainActivity : AbsSlidingMusicPanelActivity(), SharedPreferences.OnSharedP val id = parseIdFromIntent(intent, "albumId", "album").toInt() if (id >= 0) { val position = intent.getIntExtra("position", 0) - MusicPlayerRemote - .openQueue(AlbumLoader.getAlbum(this, id).blockingFirst().songs!!, position, true) + MusicPlayerRemote.openQueue(AlbumLoader.getAlbum(this, id).blockingFirst().songs!!, position, true) handled = true } } else if (MediaStore.Audio.Artists.CONTENT_TYPE == mimeType) { val id = parseIdFromIntent(intent, "artistId", "artist").toInt() if (id >= 0) { val position = intent.getIntExtra("position", 0) - MusicPlayerRemote - .openQueue(ArtistLoader.getArtist(this, id).blockingFirst().songs, position, true) + MusicPlayerRemote.openQueue(ArtistLoader.getArtist(this, id).blockingFirst().songs, position, true) handled = true } } @@ -192,7 +196,6 @@ class MainActivity : AbsSlidingMusicPanelActivity(), SharedPreferences.OnSharedP } catch (e: NumberFormatException) { Log.e(TAG, e.message) } - } } return id @@ -213,6 +216,10 @@ class MainActivity : AbsSlidingMusicPanelActivity(), SharedPreferences.OnSharedP } override fun handleBackPress(): Boolean { + if (drawerLayout.isDrawerOpen(navigationView)) { + drawerLayout.closeDrawers() + return true + } return super.handleBackPress() || currentFragment.handleBackPress() } @@ -257,10 +264,10 @@ class MainActivity : AbsSlidingMusicPanelActivity(), SharedPreferences.OnSharedP private fun showPromotionalOffer() { MaterialDialog.Builder(this) .positiveText("Buy") - .onPositive { dialog, which -> startActivity(Intent(this@MainActivity, ProVersionActivity::class.java)) } + .onPositive { _, _ -> startActivity(Intent(this@MainActivity, ProVersionActivity::class.java)) } .negativeText(android.R.string.cancel) .customView(R.layout.dialog_promotional_offer, false) - .dismissListener { dialog -> + .dismissListener { PreferenceManager.getDefaultSharedPreferences(this@MainActivity) .edit() .putBoolean("shown", true) @@ -269,19 +276,82 @@ class MainActivity : AbsSlidingMusicPanelActivity(), SharedPreferences.OnSharedP .show() } - override fun onNavigationItemSelected(menuItem: MenuItem): Boolean { - PreferenceUtil.getInstance().lastPage = menuItem.itemId - selectedFragment(menuItem.itemId) - return true - } - private fun selectedFragment(itemId: Int) { when (itemId) { - R.id.action_album, R.id.action_artist, R.id.action_playlist, R.id.action_song -> setCurrentFragment(LibraryFragment.newInstance(itemId), false, LibraryFragment.TAG) - R.id.action_home -> setCurrentFragment(BannerHomeFragment.newInstance(), false, BannerHomeFragment.TAG) + R.id.action_album, + R.id.action_artist, + R.id.action_playlist, + R.id.action_song -> setCurrentFragment(LibraryFragment.newInstance(itemId)) } } + private fun setUpNavigationView() { + val accentColor = ThemeStore.accentColor(this) + NavigationViewUtil.setItemIconColors(navigationView, ATHUtil.resolveColor(this, R.attr.iconColor, ThemeStore.textColorSecondary(this)), accentColor) + NavigationViewUtil.setItemTextColors(navigationView, ThemeStore.textColorPrimary(this), accentColor) + + checkSetUpPro() + navigationView.setNavigationItemSelectedListener { menuItem -> + drawerLayout.closeDrawers() + when (menuItem.itemId) { + R.id.nav_library -> Handler().postDelayed({ setMusicChooser(LIBRARY) }, 200) + R.id.nav_home -> Handler().postDelayed({ setMusicChooser(HOME) }, 200) + R.id.nav_folders -> Handler().postDelayed({ setMusicChooser(FOLDERS) }, 200) + R.id.buy_pro -> Handler().postDelayed({ startActivityForResult(Intent(this@MainActivity, ProVersionActivity::class.java), PURCHASE_REQUEST) }, 200) + } + true + } + } + + private fun setMusicChooser(key: Int) { + PreferenceUtil.getInstance().lastMusicChooser = key + when (key) { + LIBRARY -> { + navigationView.setCheckedItem(R.id.nav_library) + setCurrentFragment(LibraryFragment.newInstance()) + } + FOLDERS -> { + navigationView.setCheckedItem(R.id.nav_folders) + setCurrentFragment(FoldersFragment.newInstance(this)) + } + HOME -> { + navigationView.setCheckedItem(R.id.nav_home) + setCurrentFragment(BannerHomeFragment()) + } + } + } + + + private fun checkSetUpPro() { + if (App.isProVersion) { + setUpPro() + } + } + + private fun setUpPro() { + navigationView.menu.removeGroup(R.id.navigation_drawer_menu_category_buy_pro) + } + + private fun setUpDrawerLayout() { + setUpNavigationView() + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == android.R.id.home) { + if (drawerLayout.isDrawerOpen(navigationView)) { + drawerLayout.closeDrawer(navigationView) + } else { + drawerLayout.openDrawer(navigationView) + } + return true + } + return super.onOptionsItemSelected(item) + } + + private fun updateNavigationDrawerHeader() { + + } + companion object { const val APP_INTRO_REQUEST = 2323 const val LIBRARY = 1 @@ -290,5 +360,6 @@ class MainActivity : AbsSlidingMusicPanelActivity(), SharedPreferences.OnSharedP private const val TAG = "MainActivity" private const val APP_USER_INFO_REQUEST = 9003 private const val REQUEST_CODE_THEME = 9002 + private const val PURCHASE_REQUEST = 101 } } diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/PlaylistDetailActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/PlaylistDetailActivity.java deleted file mode 100644 index 724b3e51..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/ui/activities/PlaylistDetailActivity.java +++ /dev/null @@ -1,309 +0,0 @@ -package code.name.monkey.retromusic.ui.activities; - -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.TextView; - -import com.afollestad.materialcab.MaterialCab; -import com.google.android.material.appbar.AppBarLayout; -import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator; -import com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemAnimator; -import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager; -import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils; -import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView; - -import java.util.ArrayList; - -import androidx.annotation.NonNull; -import androidx.appcompat.widget.Toolbar; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import butterknife.BindView; -import butterknife.ButterKnife; -import butterknife.OnClick; -import code.name.monkey.appthemehelper.ThemeStore; -import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; -import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.helper.MusicPlayerRemote; -import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper; -import code.name.monkey.retromusic.interfaces.CabHolder; -import code.name.monkey.retromusic.loaders.PlaylistLoader; -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.mvp.contract.PlaylistSongsContract; -import code.name.monkey.retromusic.mvp.presenter.PlaylistSongsPresenter; -import code.name.monkey.retromusic.ui.activities.base.AbsSlidingMusicPanelActivity; -import code.name.monkey.retromusic.ui.adapter.song.OrderablePlaylistSongAdapter; -import code.name.monkey.retromusic.ui.adapter.song.PlaylistSongAdapter; -import code.name.monkey.retromusic.ui.adapter.song.SongAdapter; -import code.name.monkey.retromusic.util.PlaylistsUtil; -import code.name.monkey.retromusic.util.RetroColorUtil; -import code.name.monkey.retromusic.util.ViewUtil; -import code.name.monkey.retromusic.views.CollapsingFAB; - -public class PlaylistDetailActivity extends AbsSlidingMusicPanelActivity implements CabHolder, - PlaylistSongsContract.PlaylistSongsView { - - @NonNull - public static String EXTRA_PLAYLIST = "extra_playlist"; - - @BindView(R.id.recycler_view) - RecyclerView recyclerView; - - @BindView(R.id.toolbar) - Toolbar toolbar; - - @BindView(android.R.id.empty) - TextView empty; - - @BindView(R.id.action_shuffle) - CollapsingFAB shuffleButton; - - @BindView(R.id.app_bar) - AppBarLayout appBarLayout; - - @BindView(R.id.title) - TextView title; - - private Playlist playlist; - private MaterialCab cab; - private SongAdapter adapter; - private RecyclerView.Adapter wrappedAdapter; - private RecyclerViewDragDropManager recyclerViewDragDropManager; - private PlaylistSongsPresenter songsPresenter; - - @Override - protected void onCreate(Bundle savedInstanceState) { - setDrawUnderStatusBar(); - super.onCreate(savedInstanceState); - ButterKnife.bind(this); - - setStatusbarColorAuto(); - setNavigationbarColorAuto(); - setTaskDescriptionColorAuto(); - setLightNavigationBar(true); - toggleBottomNavigationView(true); - - playlist = getIntent().getExtras().getParcelable(EXTRA_PLAYLIST); - - if (playlist != null) { - songsPresenter = new PlaylistSongsPresenter(this, playlist); - } - - setUpToolBar(); - setUpRecyclerView(); - } - - @Override - protected View createContentView() { - return wrapSlidingMusicPanel(R.layout.activity_playlist_detail); - } - - private void setUpRecyclerView() { - ViewUtil.setUpFastScrollRecyclerViewColor(this, ((FastScrollRecyclerView) recyclerView), - ThemeStore.accentColor(this)); - recyclerView.setLayoutManager(new LinearLayoutManager(this)); - if (playlist instanceof AbsCustomPlaylist) { - adapter = new PlaylistSongAdapter(this, new ArrayList(), R.layout.item_list, false, - this); - recyclerView.setAdapter(adapter); - } else { - recyclerViewDragDropManager = new RecyclerViewDragDropManager(); - final GeneralItemAnimator animator = new RefactoredDefaultItemAnimator(); - adapter = new OrderablePlaylistSongAdapter(this, new ArrayList<>(), - R.layout.item_list, false, this, (fromPosition, toPosition) -> { - if (PlaylistsUtil.moveItem(PlaylistDetailActivity.this, playlist.id, fromPosition, toPosition)) { - Song song = adapter.getDataSet().remove(fromPosition); - adapter.getDataSet().add(toPosition, song); - adapter.notifyItemMoved(fromPosition, toPosition); - } - }); - wrappedAdapter = recyclerViewDragDropManager.createWrappedAdapter(adapter); - - recyclerView.setAdapter(wrappedAdapter); - recyclerView.setItemAnimator(animator); - - recyclerViewDragDropManager.attachRecyclerView(recyclerView); - } - adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { - @Override - public void onChanged() { - super.onChanged(); - checkIsEmpty(); - } - }); - recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { - @Override - public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { - super.onScrolled(recyclerView, dx, dy); - if (dy > 0) { - shuffleButton.setShowTitle(false); - } else if (dy < 0) { - shuffleButton.setShowTitle(true); - } - } - }); - } - - @Override - protected void onResume() { - super.onResume(); - songsPresenter.subscribe(); - } - - private void setUpToolBar() { - title.setText(playlist.name); - title.setTextColor(ThemeStore.textColorPrimary(this)); - shuffleButton.setColor(ThemeStore.accentColor(this)); - - int primaryColor = ThemeStore.primaryColor(this); - toolbar.setBackgroundColor(primaryColor); - appBarLayout.setBackgroundColor(primaryColor); - - setTitle(null); - setSupportActionBar(toolbar); - toolbar.setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp); - - ToolbarContentTintHelper.colorBackButton(toolbar, ThemeStore.accentColor(this)); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(playlist instanceof AbsCustomPlaylist ? R.menu.menu_smart_playlist_detail : R.menu.menu_playlist_detail, menu); - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(@NonNull MenuItem item) { - int id = item.getItemId(); - switch (id) { - case android.R.id.home: - onBackPressed(); - return true; - } - return PlaylistMenuHelper.INSTANCE.handleMenuClick(this, playlist, item); - } - - @NonNull - @Override - public MaterialCab openCab(final int menu, final MaterialCab.Callback callback) { - if (cab != null && cab.isActive()) { - cab.finish(); - } - cab = new MaterialCab(this, R.id.cab_stub) - .setMenu(menu) - .setCloseDrawableRes(R.drawable.ic_close_white_24dp) - .setBackgroundColor( - RetroColorUtil.shiftBackgroundColorForLightText(ThemeStore.primaryColor(this))) - .start(callback); - return cab; - } - - @Override - public void onBackPressed() { - if (cab != null && cab.isActive()) { - cab.finish(); - } else { - recyclerView.stopScroll(); - super.onBackPressed(); - } - } - - @Override - public void onMediaStoreChanged() { - super.onMediaStoreChanged(); - - if (!(playlist instanceof AbsCustomPlaylist)) { - // Playlist deleted - if (!PlaylistsUtil.doesPlaylistExist(this, playlist.id)) { - finish(); - return; - } - - // Playlist renamed - final String playlistName = PlaylistsUtil.getNameForPlaylist(this, playlist.id); - if (!playlistName.equals(playlist.name)) { - playlist = PlaylistLoader.INSTANCE.getPlaylist(this, playlist.id).blockingFirst(); - setToolbarTitle(playlist.name); - } - } - songsPresenter.subscribe(); - } - - private void setToolbarTitle(String title) { - //noinspection ConstantConditions - getSupportActionBar().setTitle(title); - } - - private void checkIsEmpty() { - empty.setVisibility( - adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE - ); - } - - @Override - public void onPause() { - if (recyclerViewDragDropManager != null) { - recyclerViewDragDropManager.cancelDrag(); - } - super.onPause(); - songsPresenter.unsubscribe(); - } - - @Override - public void onDestroy() { - if (recyclerViewDragDropManager != null) { - recyclerViewDragDropManager.release(); - recyclerViewDragDropManager = null; - } - - if (recyclerView != null) { - recyclerView.setItemAnimator(null); - recyclerView.setAdapter(null); - recyclerView = null; - } - - if (wrappedAdapter != null) { - WrapperAdapterUtils.releaseAll(wrappedAdapter); - wrappedAdapter = null; - } - adapter = null; - - super.onDestroy(); - } - - @Override - public void onPlayingMetaChanged() { - super.onPlayingMetaChanged(); - songsPresenter.subscribe(); - } - - @Override - public void loading() { - } - - @Override - public void showEmptyView() { - empty.setVisibility(View.VISIBLE); - } - - @Override - public void completed() { - } - - @Override - public void showData(ArrayList songs) { - adapter.swapDataSet(songs); - } - - @OnClick(R.id.action_shuffle) - public void onViewClicked() { - if (adapter.getDataSet().isEmpty()) { - return; - } - MusicPlayerRemote.INSTANCE.openAndShuffleQueue(adapter.getDataSet(), true); - } -} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/PlaylistDetailActivity.kt b/app/src/main/java/code/name/monkey/retromusic/ui/activities/PlaylistDetailActivity.kt new file mode 100644 index 00000000..07ca6e0e --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/PlaylistDetailActivity.kt @@ -0,0 +1,254 @@ +package code.name.monkey.retromusic.ui.activities + +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.View +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import butterknife.ButterKnife +import code.name.monkey.appthemehelper.ThemeStore +import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper +import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.helper.MusicPlayerRemote +import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper +import code.name.monkey.retromusic.interfaces.CabHolder +import code.name.monkey.retromusic.loaders.PlaylistLoader +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.mvp.contract.PlaylistSongsContract +import code.name.monkey.retromusic.mvp.presenter.PlaylistSongsPresenter +import code.name.monkey.retromusic.ui.activities.base.AbsSlidingMusicPanelActivity +import code.name.monkey.retromusic.ui.adapter.song.OrderablePlaylistSongAdapter +import code.name.monkey.retromusic.ui.adapter.song.PlaylistSongAdapter +import code.name.monkey.retromusic.ui.adapter.song.SongAdapter +import code.name.monkey.retromusic.util.PlaylistsUtil +import code.name.monkey.retromusic.util.RetroColorUtil +import code.name.monkey.retromusic.util.ViewUtil +import com.afollestad.materialcab.MaterialCab +import com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemAnimator +import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager +import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils +import kotlinx.android.synthetic.main.activity_playlist_detail.* +import java.util.* + +class PlaylistDetailActivity : AbsSlidingMusicPanelActivity(), CabHolder, PlaylistSongsContract.PlaylistSongsView { + + + private var playlist: Playlist? = null + private var cab: MaterialCab? = null + private lateinit var adapter: SongAdapter + private var wrappedAdapter: RecyclerView.Adapter<*>? = null + private var recyclerViewDragDropManager: RecyclerViewDragDropManager? = null + private var songsPresenter: PlaylistSongsPresenter? = null + + override fun onCreate(savedInstanceState: Bundle?) { + setDrawUnderStatusBar() + super.onCreate(savedInstanceState) + ButterKnife.bind(this) + + setStatusbarColorAuto() + setNavigationbarColorAuto() + setTaskDescriptionColorAuto() + setLightNavigationBar(true) + toggleBottomNavigationView(true) + + + playlist = intent.extras!!.getParcelable(EXTRA_PLAYLIST) + songsPresenter = PlaylistSongsPresenter(this, playlist!!) + + setUpToolBar() + setUpRecyclerView() + } + + override fun createContentView(): View { + return wrapSlidingMusicPanel(R.layout.activity_playlist_detail) + } + + private fun setUpRecyclerView() { + ViewUtil.setUpFastScrollRecyclerViewColor(this, recyclerView, ThemeStore.accentColor(this)) + recyclerView.layoutManager = LinearLayoutManager(this) + if (playlist is AbsCustomPlaylist) { + adapter = PlaylistSongAdapter(this, ArrayList(), R.layout.item_list, false, this) + recyclerView!!.adapter = adapter + } else { + recyclerViewDragDropManager = RecyclerViewDragDropManager() + val animator = RefactoredDefaultItemAnimator() + adapter = OrderablePlaylistSongAdapter(this, ArrayList(), R.layout.item_list, false, this, + object : OrderablePlaylistSongAdapter.OnMoveItemListener { + override fun onMoveItem(fromPosition: Int, toPosition: Int) { + if (PlaylistsUtil.moveItem(this@PlaylistDetailActivity, playlist!!.id, fromPosition, toPosition)) { + val song = adapter.dataSet.removeAt(fromPosition) + adapter.dataSet.add(toPosition, song) + adapter.notifyItemMoved(fromPosition, toPosition) + } + } + }) + wrappedAdapter = recyclerViewDragDropManager!!.createWrappedAdapter(adapter) + + recyclerView.adapter = wrappedAdapter + recyclerView.itemAnimator = animator + + recyclerViewDragDropManager!!.attachRecyclerView(recyclerView!!) + } + adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { + override fun onChanged() { + super.onChanged() + checkIsEmpty() + } + }) + recyclerView!!.addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + if (dy > 0) { + actionShuffle.setShowTitle(false) + } else if (dy < 0) { + actionShuffle.setShowTitle(true) + } + } + }) + actionShuffle.setOnClickListener { + if (adapter.dataSet.isEmpty()) { + return@setOnClickListener + } + MusicPlayerRemote.openAndShuffleQueue(adapter.dataSet, true) + } + } + + override fun onResume() { + super.onResume() + songsPresenter!!.subscribe() + } + + private fun setUpToolBar() { + bannerTitle.text = playlist!!.name + bannerTitle.setTextColor(ThemeStore.textColorPrimary(this)) + actionShuffle.setColor(ThemeStore.accentColor(this)) + + val primaryColor = ThemeStore.primaryColor(this) + toolbar!!.setBackgroundColor(primaryColor) + appBarLayout!!.setBackgroundColor(primaryColor) + + title = null + setSupportActionBar(toolbar) + toolbar!!.setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp) + + ToolbarContentTintHelper.colorBackButton(toolbar!!, ThemeStore.accentColor(this)) + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(if (playlist is AbsCustomPlaylist) R.menu.menu_smart_playlist_detail else R.menu.menu_playlist_detail, menu) + return super.onCreateOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + val id = item.itemId + when (id) { + android.R.id.home -> { + onBackPressed() + return true + } + } + return PlaylistMenuHelper.handleMenuClick(this, playlist!!, item) + } + + override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab { + if (cab != null && cab!!.isActive) { + cab!!.finish() + } + cab = MaterialCab(this, R.id.cab_stub) + .setMenu(menuRes) + .setCloseDrawableRes(R.drawable.ic_close_white_24dp) + .setBackgroundColor( + RetroColorUtil.shiftBackgroundColorForLightText(ThemeStore.primaryColor(this))) + .start(callback) + return cab!! + } + + override fun onBackPressed() { + if (cab != null && cab!!.isActive) { + cab!!.finish() + } else { + recyclerView!!.stopScroll() + super.onBackPressed() + } + } + + override fun onMediaStoreChanged() { + super.onMediaStoreChanged() + + if (playlist !is AbsCustomPlaylist) { + // Playlist deleted + if (!PlaylistsUtil.doesPlaylistExist(this, playlist!!.id)) { + finish() + return + } + + // Playlist renamed + val playlistName = PlaylistsUtil.getNameForPlaylist(this, playlist!!.id.toLong()) + if (playlistName != playlist!!.name) { + playlist = PlaylistLoader.getPlaylist(this, playlist!!.id).blockingFirst() + setToolbarTitle(playlist!!.name) + } + } + songsPresenter!!.subscribe() + } + + private fun setToolbarTitle(title: String) { + + supportActionBar!!.title = title + } + + private fun checkIsEmpty() { + empty.visibility = if (adapter.itemCount == 0) View.VISIBLE else View.GONE + } + + public override fun onPause() { + if (recyclerViewDragDropManager != null) { + recyclerViewDragDropManager!!.cancelDrag() + } + super.onPause() + songsPresenter!!.unsubscribe() + } + + override fun onDestroy() { + if (recyclerViewDragDropManager != null) { + recyclerViewDragDropManager!!.release() + recyclerViewDragDropManager = null + } + + if (recyclerView != null) { + recyclerView!!.itemAnimator = null + recyclerView!!.adapter = null + } + + if (wrappedAdapter != null) { + WrapperAdapterUtils.releaseAll(wrappedAdapter) + wrappedAdapter = null + } + super.onDestroy() + } + + override fun onPlayingMetaChanged() { + super.onPlayingMetaChanged() + songsPresenter!!.subscribe() + } + + override fun loading() {} + + override fun showEmptyView() { + empty!!.visibility = View.VISIBLE + } + + override fun completed() {} + + override fun showData(songs: ArrayList) { + adapter.swapDataSet(songs) + } + + companion object { + + var EXTRA_PLAYLIST = "extra_playlist" + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/ProVersionActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/ProVersionActivity.java index e2a8ea95..cd425bd6 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/activities/ProVersionActivity.java +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/ProVersionActivity.java @@ -27,7 +27,7 @@ import code.name.monkey.appthemehelper.util.MaterialUtil; import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; import code.name.monkey.retromusic.BuildConfig; import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.RetroApplication; +import code.name.monkey.retromusic.App; import code.name.monkey.retromusic.ui.activities.base.AbsBaseActivity; /** @@ -106,7 +106,7 @@ public class ProVersionActivity extends AbsBaseActivity implements } break; case R.id.purchase_button: - billingProcessor.purchase(ProVersionActivity.this, RetroApplication.PRO_VERSION_PRODUCT_ID); + billingProcessor.purchase(ProVersionActivity.this, App.PRO_VERSION_PRODUCT_ID); break; } } @@ -119,7 +119,7 @@ public class ProVersionActivity extends AbsBaseActivity implements @Override public void onPurchaseHistoryRestored() { - if (RetroApplication.Companion.isProVersion()) { + if (App.Companion.isProVersion()) { Toast.makeText(this, R.string.restored_previous_purchase_please_restart, Toast.LENGTH_LONG) .show(); setResult(RESULT_OK); diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/SettingsActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/SettingsActivity.java deleted file mode 100755 index b0e912ab..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/ui/activities/SettingsActivity.java +++ /dev/null @@ -1,163 +0,0 @@ -package code.name.monkey.retromusic.ui.activities; - -import android.content.SharedPreferences; -import android.os.Bundle; -import android.util.Log; -import android.view.MenuItem; -import android.widget.FrameLayout; -import android.widget.TextView; - -import com.afollestad.materialdialogs.color.ColorChooserDialog; -import com.google.android.material.appbar.AppBarLayout; - -import androidx.annotation.ColorInt; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import androidx.appcompat.widget.Toolbar; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentTransaction; -import androidx.transition.TransitionManager; -import butterknife.BindView; -import butterknife.ButterKnife; -import code.name.monkey.appthemehelper.ThemeStore; -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.ui.activities.base.AbsBaseActivity; -import code.name.monkey.retromusic.ui.fragments.settings.MainSettingsFragment; -import code.name.monkey.retromusic.util.PreferenceUtil; - -public class SettingsActivity extends AbsBaseActivity implements ColorChooserDialog.ColorCallback, SharedPreferences.OnSharedPreferenceChangeListener { - - private static final String TAG = "SettingsActivity"; - - @BindView(R.id.toolbar) - Toolbar toolbar; - - @BindView(R.id.app_bar) - AppBarLayout appBarLayout; - - @BindView(R.id.title) - TextView title; - - @BindView(R.id.detail_content_frame) - @Nullable - FrameLayout detailsFrame; - - private FragmentManager fragmentManager = getSupportFragmentManager(); - - @Override - public void onColorSelection(@NonNull ColorChooserDialog dialog, @ColorInt int selectedColor) { - switch (dialog.getTitle()) { - case R.string.primary_color: - int theme = ColorUtil.isColorLight(selectedColor) ? - PreferenceUtil.getThemeResFromPrefValue("light") : - PreferenceUtil.getThemeResFromPrefValue("dark"); - - ThemeStore.editTheme(this).activityTheme(theme).primaryColor(selectedColor).commit(); - break; - case R.string.accent_color: - ThemeStore.editTheme(this).accentColor(selectedColor).commit(); - break; - } - - recreate(); - } - - @Override - public void onColorChooserDismissed(@NonNull ColorChooserDialog dialog) { - - } - - @Override - protected void onCreate(@Nullable Bundle bundle) { - super.onCreate(bundle); - setContentView(R.layout.activity_settings); - ButterKnife.bind(this); - - setStatusbarColorAuto(); - setNavigationbarColorAuto(); - - setLightNavigationBar(true); - - setupToolbar(); - - if (bundle == null) { - fragmentManager.beginTransaction().replace(R.id.content_frame, new MainSettingsFragment()) - .commit(); - } - } - - private void setupToolbar() { - toolbar.setBackgroundColor(ThemeStore.primaryColor(this)); - appBarLayout.setBackgroundColor(ThemeStore.primaryColor(this)); - setSupportActionBar(toolbar); - setTitle(null); - toolbar.setNavigationOnClickListener(v -> onBackPressed()); - title.setTextColor(ThemeStore.textColorPrimary(this)); - ToolbarContentTintHelper.colorBackButton(toolbar, ThemeStore.accentColor(this)); - } - - public void setupFragment(Fragment fragment, @StringRes int titleName) { - FragmentTransaction fragmentTransaction = fragmentManager - .beginTransaction() - .setCustomAnimations(R.anim.sliding_in_left, R.anim.sliding_out_right, android.R.anim.slide_in_left, android.R.anim.slide_out_right); - - title.setText(titleName); - - if (detailsFrame == null) { - fragmentTransaction.replace(R.id.content_frame, fragment, fragment.getTag()); - fragmentTransaction.addToBackStack(null); - fragmentTransaction.commit(); - } else { - fragmentTransaction.replace(R.id.detail_content_frame, fragment, fragment.getTag()); - fragmentTransaction.commit(); - } - } - - @Override - public void onBackPressed() { - if (fragmentManager.getBackStackEntryCount() == 0) { - super.onBackPressed(); - } else { - title.setText(R.string.action_settings); - fragmentManager.popBackStack(); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - onBackPressed(); - return true; - } - return super.onOptionsItemSelected(item); - } - - public void addAppbarLayoutElevation(float v) { - TransitionManager.beginDelayedTransition(appBarLayout); - appBarLayout.setElevation(v); - } - - @Override - public void onPause() { - super.onPause(); - PreferenceUtil.getInstance().unregisterOnSharedPreferenceChangedListener(this); - } - - @Override - public void onResume() { - super.onResume(); - PreferenceUtil.getInstance().registerOnSharedPreferenceChangedListener(this); - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - Log.i(TAG, "onSharedPreferenceChanged: "); - if (key.equals(PreferenceUtil.PROFILE_IMAGE_PATH)) { - recreate(); - } - } -} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/SettingsActivity.kt b/app/src/main/java/code/name/monkey/retromusic/ui/activities/SettingsActivity.kt new file mode 100755 index 00000000..e0fe1e00 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/SettingsActivity.kt @@ -0,0 +1,124 @@ +package code.name.monkey.retromusic.ui.activities + +import android.content.SharedPreferences +import android.os.Bundle +import android.view.MenuItem +import androidx.annotation.ColorInt +import androidx.annotation.StringRes +import androidx.fragment.app.Fragment +import butterknife.ButterKnife +import code.name.monkey.appthemehelper.ThemeStore +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.ui.activities.base.AbsBaseActivity +import code.name.monkey.retromusic.ui.fragments.settings.MainSettingsFragment +import code.name.monkey.retromusic.util.PreferenceUtil +import com.afollestad.materialdialogs.color.ColorChooserDialog +import kotlinx.android.synthetic.main.activity_settings.* + +class SettingsActivity : AbsBaseActivity(), ColorChooserDialog.ColorCallback, SharedPreferences.OnSharedPreferenceChangeListener { + + private val fragmentManager = supportFragmentManager + + override fun onColorSelection(dialog: ColorChooserDialog, @ColorInt selectedColor: Int) { + when (dialog.title) { + R.string.primary_color -> { + val theme = if (ColorUtil.isColorLight(selectedColor)) + PreferenceUtil.getThemeResFromPrefValue("light") + else + PreferenceUtil.getThemeResFromPrefValue("dark") + + ThemeStore.editTheme(this).activityTheme(theme).primaryColor(selectedColor).commit() + } + R.string.accent_color -> ThemeStore.editTheme(this).accentColor(selectedColor).commit() + } + recreate() + } + + override fun onColorChooserDismissed(dialog: ColorChooserDialog) { + + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_settings) + ButterKnife.bind(this) + + setStatusbarColorAuto() + setNavigationbarColorAuto() + + setLightNavigationBar(true) + + setupToolbar() + + if (savedInstanceState == null) { + fragmentManager.beginTransaction().replace(R.id.contentFrame, MainSettingsFragment()) + .commit() + } + } + + private fun setupToolbar() { + setSupportActionBar(toolbar) + title = null + toolbar.setBackgroundColor(ThemeStore.primaryColor(this)) + appBarLayout.setBackgroundColor(ThemeStore.primaryColor(this)) + toolbar.setNavigationOnClickListener { onBackPressed() } + settingsTitle.setTextColor(ThemeStore.textColorPrimary(this)) + ToolbarContentTintHelper.colorBackButton(toolbar, ThemeStore.accentColor(this)) + } + + fun setupFragment(fragment: Fragment, @StringRes titleName: Int) { + val fragmentTransaction = fragmentManager + .beginTransaction() + .setCustomAnimations(R.anim.sliding_in_left, R.anim.sliding_out_right, android.R.anim.slide_in_left, android.R.anim.slide_out_right) + + settingsTitle.setText(titleName) + + if (detailContentFrame == null) { + fragmentTransaction.replace(R.id.contentFrame, fragment, fragment.tag) + fragmentTransaction.addToBackStack(null) + fragmentTransaction.commit() + } else { + fragmentTransaction.replace(R.id.detailContentFrame, fragment, fragment.tag) + fragmentTransaction.commit() + } + } + + override fun onBackPressed() { + if (fragmentManager.backStackEntryCount == 0) { + super.onBackPressed() + } else { + settingsTitle.setText(R.string.action_settings) + fragmentManager.popBackStack() + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == android.R.id.home) { + onBackPressed() + return true + } + return super.onOptionsItemSelected(item) + } + + public override fun onPause() { + super.onPause() + PreferenceUtil.getInstance().unregisterOnSharedPreferenceChangedListener(this) + } + + public override fun onResume() { + super.onResume() + PreferenceUtil.getInstance().registerOnSharedPreferenceChangedListener(this) + } + + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { + if (key == PreferenceUtil.PROFILE_IMAGE_PATH) { + recreate() + } + } + + companion object { + const val TAG: String = "SettingsActivity" + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/WhatsNewActivity.kt b/app/src/main/java/code/name/monkey/retromusic/ui/activities/WhatsNewActivity.kt index f70c5e94..d8a6d492 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/activities/WhatsNewActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/WhatsNewActivity.kt @@ -46,7 +46,7 @@ class WhatsNewActivity : AbsBaseActivity() { appBarLayout.setBackgroundColor(ThemeStore.primaryColor(this)) setSupportActionBar(toolbar) title = null - toolbar.setNavigationOnClickListener({ v -> onBackPressed() }) + toolbar.setNavigationOnClickListener { onBackPressed() } whatNewtitle.setTextColor(ThemeStore.textColorPrimary(this)) ToolbarContentTintHelper.colorBackButton(toolbar, ThemeStore.accentColor(this)) diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsBaseActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsBaseActivity.java deleted file mode 100644 index 1c679cff..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsBaseActivity.java +++ /dev/null @@ -1,159 +0,0 @@ -package code.name.monkey.retromusic.ui.activities.base; - -import android.Manifest; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.media.AudioManager; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.provider.Settings; -import android.view.KeyEvent; -import android.view.View; -import android.view.ViewGroup; - -import com.google.android.material.snackbar.Snackbar; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.app.ActivityCompat; -import androidx.core.view.ViewCompat; -import code.name.monkey.appthemehelper.ThemeStore; -import code.name.monkey.retromusic.R; -import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper; - - -public abstract class AbsBaseActivity extends AbsThemeActivity { - - public static final int PERMISSION_REQUEST = 100; - private boolean hadPermissions; - private String[] permissions; - private String permissionDeniedMessage; - - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setVolumeControlStream(AudioManager.STREAM_MUSIC); - - permissions = getPermissionsToRequest(); - hadPermissions = hasPermissions(); - - setPermissionDeniedMessage(null); - } - - @Override - protected void onPostCreate(@Nullable Bundle savedInstanceState) { - super.onPostCreate(savedInstanceState); - if (!hasPermissions()) { - requestPermissions(); - } - } - - @Override - protected void onResume() { - super.onResume(); - final boolean hasPermissions = hasPermissions(); - if (hasPermissions != hadPermissions) { - hadPermissions = hasPermissions; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - onHasPermissionsChanged(hasPermissions); - } - } - } - - protected void onHasPermissionsChanged(boolean hasPermissions) { - // implemented by sub classes - } - - @Override - public boolean dispatchKeyEvent(@NonNull KeyEvent event) { - if (event.getKeyCode() == KeyEvent.KEYCODE_MENU && event.getAction() == KeyEvent.ACTION_UP) { - showOverflowMenu(); - return true; - } - return super.dispatchKeyEvent(event); - } - - protected void showOverflowMenu() { - - } - - @Override - protected void attachBaseContext(Context newBase) { - super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase)); - } - - @Nullable - protected String[] getPermissionsToRequest() { - return null; - } - - protected View getSnackBarContainer() { - return getWindow().getDecorView(); - } - - private String getPermissionDeniedMessage() { - return permissionDeniedMessage == null ? getString(R.string.permissions_denied) - : permissionDeniedMessage; - } - - protected void setPermissionDeniedMessage(String message) { - permissionDeniedMessage = message; - } - - protected void requestPermissions() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && permissions != null) { - requestPermissions(permissions, PERMISSION_REQUEST); - } - } - - protected boolean hasPermissions() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && permissions != null) { - for (String permission : permissions) { - if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { - return false; - } - } - } - return true; - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, - @NonNull int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - if (requestCode == PERMISSION_REQUEST) { - for (int grantResult : grantResults) { - if (grantResult != PackageManager.PERMISSION_GRANTED) { - if (ActivityCompat.shouldShowRequestPermissionRationale(AbsBaseActivity.this, - Manifest.permission.WRITE_EXTERNAL_STORAGE)) { - //User has deny from permission dialog - Snackbar.make(getSnackBarContainer(), getPermissionDeniedMessage(), - Snackbar.LENGTH_INDEFINITE) - .setAction(R.string.action_grant, view -> requestPermissions()) - .setActionTextColor(ThemeStore.accentColor(this)) - .show(); - } else { - // User has deny permission and checked never show permission dialog so you can redirect to Application settings page - Snackbar.make(getSnackBarContainer(), getPermissionDeniedMessage(), - Snackbar.LENGTH_INDEFINITE) - .setAction(R.string.action_settings, view -> { - Intent intent = new Intent(); - intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); - Uri uri = Uri.fromParts("package", AbsBaseActivity.this.getPackageName(), null); - intent.setData(uri); - startActivity(intent); - }) - .setActionTextColor(ThemeStore.accentColor(this)) - .show(); - } - return; - } - } - hadPermissions = true; - onHasPermissionsChanged(true); - } - } -} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsBaseActivity.kt b/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsBaseActivity.kt index 25850c50..5bbafb05 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsBaseActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsBaseActivity.kt @@ -1,7 +1,6 @@ package code.name.monkey.retromusic.ui.activities.base import android.Manifest -import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.media.AudioManager @@ -15,17 +14,16 @@ import androidx.core.app.ActivityCompat import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.retromusic.R import com.google.android.material.snackbar.Snackbar -import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper abstract class AbsBaseActivity : AbsThemeActivity() { private var hadPermissions: Boolean = false - private var permissions: Array? = null + private lateinit var permissions: Array private var permissionDeniedMessage: String? = null - open fun getPermissionsToRequest(): Array? { - return null + open fun getPermissionsToRequest(): Array { + return arrayOf() } protected fun setPermissionDeniedMessage(message: String) { @@ -82,20 +80,16 @@ abstract class AbsBaseActivity : AbsThemeActivity() { protected fun showOverflowMenu() { } - - override fun attachBaseContext(newBase: Context) { - super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase)) - } - + protected open fun requestPermissions() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - requestPermissions(permissions!!, PERMISSION_REQUEST) + requestPermissions(permissions, PERMISSION_REQUEST) } } protected fun hasPermissions(): Boolean { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - for (permission in permissions!!) { + for (permission in permissions) { if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { return false } diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsMusicServiceActivity.kt b/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsMusicServiceActivity.kt index 086c9786..e22fb801 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsMusicServiceActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsMusicServiceActivity.kt @@ -138,7 +138,7 @@ abstract class AbsMusicServiceActivity : AbsCastActivity(), MusicServiceEventLis } - override fun getPermissionsToRequest(): Array? { + override fun getPermissionsToRequest(): Array { return arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE) } @@ -163,6 +163,6 @@ abstract class AbsMusicServiceActivity : AbsCastActivity(), MusicServiceEventLis } companion object { - val TAG = AbsMusicServiceActivity::class.java.simpleName + val TAG: String = AbsMusicServiceActivity::class.java.simpleName } } diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsSlidingMusicPanelActivity.kt b/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsSlidingMusicPanelActivity.kt index d41255e7..6035d437 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsSlidingMusicPanelActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsSlidingMusicPanelActivity.kt @@ -21,6 +21,10 @@ import code.name.monkey.retromusic.ui.fragments.NowPlayingScreen import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.ui.fragments.player.adaptive.AdaptiveFragment import code.name.monkey.retromusic.ui.fragments.player.blur.BlurPlayerFragment +import code.name.monkey.retromusic.ui.fragments.player.card.CardFragment +import code.name.monkey.retromusic.ui.fragments.player.cardblur.CardBlurFragment +import code.name.monkey.retromusic.ui.fragments.player.fit.FitFragment +import code.name.monkey.retromusic.ui.fragments.player.normal.PlayerFragment import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.views.BottomNavigationBarTinted import com.google.android.material.bottomnavigation.BottomNavigationView @@ -58,7 +62,7 @@ abstract class AbsSlidingMusicPanelActivity protected constructor() : AbsMusicSe slidingUpPanelLayout = findViewById(R.id.sliding_layout); bottomNavigationView = findViewById(R.id.bottom_navigation); - choosFragmentForTheme() + chooseFragmentForTheme() setupSlidingUpPanel() } @@ -207,22 +211,23 @@ abstract class AbsSlidingMusicPanelActivity protected constructor() : AbsMusicSe miniPlayerFragment!!.view!!.visibility = if (alpha == 0f) View.GONE else View.VISIBLE } - private fun choosFragmentForTheme() { + private fun chooseFragmentForTheme() { currentNowPlayingScreen = PreferenceUtil.getInstance().nowPlayingScreen - val fragment: Fragment // must implement AbsPlayerFragment - when (currentNowPlayingScreen) { - NowPlayingScreen.BLUR -> fragment = BlurPlayerFragment() - NowPlayingScreen.ADAPTIVE -> fragment = AdaptiveFragment() - else -> fragment = AdaptiveFragment() - } + val fragment: Fragment = when (currentNowPlayingScreen) { + NowPlayingScreen.BLUR -> BlurPlayerFragment() + NowPlayingScreen.ADAPTIVE -> AdaptiveFragment() + NowPlayingScreen.NORMAL -> PlayerFragment() + NowPlayingScreen.CARD -> CardFragment() + NowPlayingScreen.BLUR_CARD -> CardBlurFragment() + NowPlayingScreen.FIT -> FitFragment() + else -> PlayerFragment() + } // must implement AbsPlayerFragment supportFragmentManager.beginTransaction().replace(R.id.player_fragment_container, fragment).commit() supportFragmentManager.executePendingTransactions() - playerFragment = supportFragmentManager.findFragmentById(R.id.player_fragment_container) as AbsPlayerFragment? - miniPlayerFragment = supportFragmentManager.findFragmentById(R.id.mini_player_fragment) as MiniPlayerFragment? - - + playerFragment = supportFragmentManager.findFragmentById(R.id.player_fragment_container) as AbsPlayerFragment + miniPlayerFragment = supportFragmentManager.findFragmentById(R.id.mini_player_fragment) as MiniPlayerFragment miniPlayerFragment!!.view!!.setOnClickListener { expandPanel() } } @@ -296,6 +301,6 @@ abstract class AbsSlidingMusicPanelActivity protected constructor() : AbsMusicSe companion object { - val TAG = AbsSlidingMusicPanelActivity::class.java.simpleName + val TAG: String = AbsSlidingMusicPanelActivity::class.java.simpleName } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsThemeActivity.kt b/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsThemeActivity.kt index a88a03e6..fb99ef1b 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsThemeActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsThemeActivity.kt @@ -96,15 +96,16 @@ abstract class AbsThemeActivity : ATHActivity(), Runnable { if (VersionUtils.hasKitKat()) { val statusBar = window.decorView.rootView.findViewById(R.id.status_bar) if (statusBar != null) { - if (VersionUtils.hasMarshmallow()) { - window.statusBarColor = color - } else if (VersionUtils.hasLollipop()) { - statusBar.setBackgroundColor(ColorUtil.darkenColor(color)) - } else { - statusBar.setBackgroundColor(color) + when { + VersionUtils.hasMarshmallow() -> window.statusBarColor = color + VersionUtils.hasLollipop() -> statusBar.setBackgroundColor(ColorUtil.darkenColor(color)) + else -> statusBar.setBackgroundColor(color) } } else if (Build.VERSION.SDK_INT >= 21) { - window.statusBarColor = ColorUtil.darkenColor(color) + when { + VersionUtils.hasMarshmallow() -> window.statusBarColor = color + else -> window.statusBarColor = ColorUtil.darkenColor(color) + } } } setLightStatusbarAuto(color) diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/adapter/CollageSongAdapter.java b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/CollageSongAdapter.java index 4e955f2c..166f7f65 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/adapter/CollageSongAdapter.java +++ b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/CollageSongAdapter.java @@ -70,8 +70,7 @@ public class CollageSongAdapter extends RecyclerView.Adapter imageViews; @@ -80,14 +79,12 @@ public class CollageSongAdapter extends RecyclerView.Adapter { - MusicPlayerRemote.INSTANCE.openQueue(dataSet, 0, true); - }); - view.setBackgroundColor(color); - view.setTextColor(MaterialValueHelper.getPrimaryTextColor(context, ColorUtil.isColorLight(color))); + //ButterKnife.bind(this, itemView); + //Context context = itemView.getContext(); + //int color = ThemeStore.accentColor(context); + //view.setOnClickListener(v -> MusicPlayerRemote.INSTANCE.openQueue(dataSet, 0, true)); + //view.setBackgroundColor(color); + //view.setTextColor(MaterialValueHelper.getPrimaryTextColor(context, ColorUtil.isColorLight(color))); } void bindSongs() { diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/AbsOffsetSongAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/AbsOffsetSongAdapter.kt index 4329ea2b..92e25f58 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/AbsOffsetSongAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/AbsOffsetSongAdapter.kt @@ -61,7 +61,7 @@ abstract class AbsOffsetSongAdapter : SongAdapter { override// could also return null, just to be safe return empty song val song: Song - get() = if (itemViewType == OFFSET_ITEM) Song.EMPTY_SONG else dataSet[adapterPosition - 1] + get() = if (itemViewType == OFFSET_ITEM) Song.emptySong else dataSet[adapterPosition - 1] override fun onClick(v: View?) { if (isInQuickSelectMode && itemViewType != OFFSET_ITEM) { diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/OrderablePlaylistSongAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/OrderablePlaylistSongAdapter.kt index a55a548f..8889c516 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/OrderablePlaylistSongAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/OrderablePlaylistSongAdapter.kt @@ -36,7 +36,7 @@ class OrderablePlaylistSongAdapter(activity: AppCompatActivity, positionFinal-- var long: Long = 0 - if (position < 0) { + if (positionFinal < 0) { long = -2 } else { if (dataSet[positionFinal] is PlaylistSong) { diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/MiniPlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/MiniPlayerFragment.kt index d627fd68..34d90346 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/MiniPlayerFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/MiniPlayerFragment.kt @@ -10,7 +10,6 @@ import android.text.SpannableStringBuilder import android.text.style.ForegroundColorSpan import android.view.* import android.view.animation.DecelerateInterpolator -import butterknife.OnClick import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.retromusic.R import code.name.monkey.retromusic.helper.MusicPlayerRemote @@ -76,7 +75,7 @@ open class MiniPlayerFragment : AbsMusicServiceFragment(), MusicProgressViewUpda private fun updateSongTitle() { val builder = SpannableStringBuilder() - val song = MusicPlayerRemote.currentSong ?: return + val song = MusicPlayerRemote.currentSong val title = SpannableString(song.title) title.setSpan(ForegroundColorSpan(ThemeStore.textColorPrimary(context!!)), 0, title.length, 0) diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/NowPlayingScreen.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/NowPlayingScreen.java index 59f4288e..5b74e577 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/NowPlayingScreen.java +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/NowPlayingScreen.java @@ -6,9 +6,19 @@ import code.name.monkey.retromusic.R; public enum NowPlayingScreen { - + NORMAL(R.string.normal, R.drawable.np_normal, 0), + FLAT(R.string.flat, R.drawable.np_flat, 1), + //FULL(R.string.full, R.drawable.np_full, 2), + //PLAIN(R.string.plain, R.drawable.np_plain, 3), + BLUR(R.string.blur, R.drawable.np_blur, 4), + //COLOR(R.string.color, R.drawable.np_color, 5), + CARD(R.string.card, R.drawable.np_card, 6), + //TINY(R.string.tiny, R.drawable.np_tiny, 7), + //SIMPLE(R.string.simple, R.drawable.np_simple, 8), + BLUR_CARD(R.string.blur_card, R.drawable.np_blur_card, 9), ADAPTIVE(R.string.adaptive, R.drawable.np_adaptive, 10), - BLUR(R.string.blur, R.drawable.np_blur, 4); + //MATERIAL(R.string.material, R.drawable.np_material, 11), + FIT(R.string.fit, R.drawable.np_adaptive, 12); @StringRes public final int titleRes; diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/PlayingQueueFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/PlayingQueueFragment.java deleted file mode 100644 index 6f6865d7..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/PlayingQueueFragment.java +++ /dev/null @@ -1,148 +0,0 @@ -package code.name.monkey.retromusic.ui.fragments; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator; -import com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemAnimator; -import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager; -import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import butterknife.BindView; -import butterknife.ButterKnife; -import butterknife.Unbinder; -import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.helper.MusicPlayerRemote; -import code.name.monkey.retromusic.ui.adapter.song.PlayingQueueAdapter; -import code.name.monkey.retromusic.ui.fragments.base.AbsMusicServiceFragment; -import code.name.monkey.retromusic.views.CollapsingFAB; - -public class PlayingQueueFragment extends AbsMusicServiceFragment { - - @BindView(R.id.recycler_view) - RecyclerView mRecyclerView; - - - Unbinder unbinder; - - private RecyclerView.Adapter mWrappedAdapter; - private RecyclerViewDragDropManager mRecyclerViewDragDropManager; - private PlayingQueueAdapter mPlayingQueueAdapter; - private LinearLayoutManager mLayoutManager; - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_main_activity_recycler_view, container, false); - unbinder = ButterKnife.bind(this, view); - return view; - } - - @Override - public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - setUpRecyclerView(); - } - - private void setUpRecyclerView() { - mRecyclerViewDragDropManager = new RecyclerViewDragDropManager(); - final GeneralItemAnimator animator = new RefactoredDefaultItemAnimator(); - - mPlayingQueueAdapter = new PlayingQueueAdapter( - (AppCompatActivity) getActivity(), - MusicPlayerRemote.INSTANCE.getPlayingQueue(), - MusicPlayerRemote.INSTANCE.getPosition(), - R.layout.item_queue); - mWrappedAdapter = mRecyclerViewDragDropManager.createWrappedAdapter(mPlayingQueueAdapter); - - mLayoutManager = new LinearLayoutManager(getContext()); - - mRecyclerView.setLayoutManager(mLayoutManager); - mRecyclerView.setAdapter(mWrappedAdapter); - mRecyclerView.setItemAnimator(animator); - mRecyclerViewDragDropManager.attachRecyclerView(mRecyclerView); - mLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.INSTANCE.getPosition() + 1, 0); - - } - - @Override - public void onQueueChanged() { - updateQueue(); - updateCurrentSong(); - } - - @Override - public void onMediaStoreChanged() { - updateQueue(); - updateCurrentSong(); - } - - @SuppressWarnings("ConstantConditions") - private void updateCurrentSong() { - } - - @Override - public void onPlayingMetaChanged() { - //updateCurrentSong(); - //updateIsFavorite(); - updateQueuePosition(); - //updateLyrics(); - } - - private void updateQueuePosition() { - mPlayingQueueAdapter.setCurrent(MusicPlayerRemote.INSTANCE.getPosition()); - // if (slidingUpPanelLayout.getPanelState() == SlidingUpPanelLayout.PanelState.COLLAPSED) { - resetToCurrentPosition(); - //} - } - - private void updateQueue() { - mPlayingQueueAdapter - .swapDataSet(MusicPlayerRemote.INSTANCE.getPlayingQueue(), MusicPlayerRemote.INSTANCE.getPosition()); - resetToCurrentPosition(); - } - - private void resetToCurrentPosition() { - mRecyclerView.stopScroll(); - mLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.INSTANCE.getPosition() + 1, 0); - } - - @Override - public void onPause() { - if (mRecyclerViewDragDropManager != null) { - mRecyclerViewDragDropManager.cancelDrag(); - } - super.onPause(); - } - - @Override - public void onDestroyView() { - if (mRecyclerViewDragDropManager != null) { - mRecyclerViewDragDropManager.release(); - mRecyclerViewDragDropManager = null; - } - - if (mRecyclerView != null) { - mRecyclerView.setItemAnimator(null); - mRecyclerView.setAdapter(null); - mRecyclerView = null; - } - - if (mWrappedAdapter != null) { - WrapperAdapterUtils.releaseAll(mWrappedAdapter); - mWrappedAdapter = null; - } - mPlayingQueueAdapter = null; - mLayoutManager = null; - super.onDestroyView(); - unbinder.unbind(); - } -} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/VolumeFragment.kt b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/VolumeFragment.kt index 3fb07eea..5202281e 100755 --- a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/VolumeFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/VolumeFragment.kt @@ -47,19 +47,19 @@ class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolum val audioManager = audioManager if (audioManager != null) { - volumeSeekBar!!.max = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) - volumeSeekBar!!.progress = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) + volumeSeekBar.max = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) + volumeSeekBar.progress = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) } - volumeSeekBar!!.setOnSeekBarChangeListener(this) + volumeSeekBar.setOnSeekBarChangeListener(this) } override fun onAudioVolumeChanged(currentVolume: Int, maxVolume: Int) { if (volumeSeekBar == null) { return } - volumeSeekBar!!.max = maxVolume - volumeSeekBar!!.progress = currentVolume - volumeDown!!.setImageResource(if (currentVolume == 0) R.drawable.ic_volume_off_white_24dp else R.drawable.ic_volume_down_white_24dp) + volumeSeekBar.max = maxVolume + volumeSeekBar.progress = currentVolume + volumeDown.setImageResource(if (currentVolume == 0) R.drawable.ic_volume_off_white_24dp else R.drawable.ic_volume_down_white_24dp) } override fun onDestroyView() { @@ -98,9 +98,9 @@ class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolum } private fun setProgressBarColor(newColor: Int) { - TintHelper.setTintAuto(volumeSeekBar!!, newColor, false) - volumeDown!!.setColorFilter(newColor, PorterDuff.Mode.SRC_IN) - volumeUp!!.setColorFilter(newColor, PorterDuff.Mode.SRC_IN) + TintHelper.setTintAuto(volumeSeekBar, newColor, false) + volumeDown.setColorFilter(newColor, PorterDuff.Mode.SRC_IN) + volumeUp.setColorFilter(newColor, PorterDuff.Mode.SRC_IN) } private fun setTintable(color: Int) { @@ -108,7 +108,7 @@ class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolum } fun removeThumb() { - volumeSeekBar!!.thumb = null + volumeSeekBar.thumb = null } private fun setPauseWhenZeroVolume(pauseWhenZeroVolume: Boolean) { diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsPlayerControlsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsPlayerControlsFragment.kt index efe3631c..239e3103 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsPlayerControlsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsPlayerControlsFragment.kt @@ -55,7 +55,7 @@ abstract class AbsPlayerControlsFragment : AbsMusicServiceFragment(), MusicProgr } - var prevButton: ImageButton? = null + /* var prevButton: ImageButton? = null var nextButton: ImageButton? = null var repeatButton: ImageButton? = null var shuffleButton: ImageButton? = null @@ -63,11 +63,12 @@ abstract class AbsPlayerControlsFragment : AbsMusicServiceFragment(), MusicProgr var songTotalTime: TextView? = null var songCurrentProgress: TextView? = null var volumeContainer: View? = null - var playPauseFab: ImageButton? = null + var playPauseFab: ImageButton? = null*/ override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - playPauseFab = view.findViewById(R.id.player_play_pause_button) + + /* playPauseFab = view.findViewById(R.id.player_play_pause_button) prevButton = view.findViewById(R.id.player_prev_button) nextButton = view.findViewById(R.id.player_next_button) repeatButton = view.findViewById(R.id.player_repeat_button) @@ -75,6 +76,6 @@ abstract class AbsPlayerControlsFragment : AbsMusicServiceFragment(), MusicProgr progressSlider = view.findViewById(R.id.player_progress_slider) songTotalTime = view.findViewById(R.id.player_song_total_time) songCurrentProgress = view.findViewById(R.id.player_song_current_progress) - volumeContainer = view.findViewById(R.id.volume_fragment_container) + volumeContainer = view.findViewById(R.id.volume_fragment_container)*/ } } diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsPlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsPlayerFragment.kt index 4eff06f4..80286a76 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsPlayerFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsPlayerFragment.kt @@ -32,8 +32,6 @@ abstract class AbsPlayerFragment : AbsMusicServiceFragment(), Toolbar.OnMenuItem private set private var updateIsFavoriteTask: AsyncTask<*, *, *>? = null - protected var toolbar: Toolbar? = null - override fun onAttach(context: Context?) { super.onAttach(context) @@ -146,6 +144,8 @@ abstract class AbsPlayerFragment : AbsMusicServiceFragment(), Toolbar.OnMenuItem MusicUtil.toggleFavorite(activity!!, song) } + abstract fun toolbarGet(): Toolbar + abstract fun onShow() abstract fun onHide() @@ -193,7 +193,7 @@ abstract class AbsPlayerFragment : AbsMusicServiceFragment(), Toolbar.OnMenuItem else R.drawable.ic_favorite_border_white_24dp val drawable = RetroUtil.getTintedVectorDrawable(activity, res, toolbarIconColor()) - //toolbar!!.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) + toolbarGet().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) } } }.execute(MusicPlayerRemote.currentSong) diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/GenreFragment.kt b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/GenreFragment.kt index 84054572..519cdc3f 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/GenreFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/GenreFragment.kt @@ -1,12 +1,9 @@ package code.name.monkey.retromusic.ui.fragments.mainactivity import android.os.Bundle -import androidx.recyclerview.widget.LinearLayoutManager import android.view.Menu import android.view.MenuInflater - -import java.util.ArrayList - +import androidx.recyclerview.widget.LinearLayoutManager import code.name.monkey.retromusic.R import code.name.monkey.retromusic.model.Genre import code.name.monkey.retromusic.mvp.contract.GenreContract @@ -14,6 +11,7 @@ import code.name.monkey.retromusic.mvp.presenter.GenrePresenter import code.name.monkey.retromusic.ui.adapter.GenreAdapter import code.name.monkey.retromusic.ui.fragments.base.AbsLibraryPagerRecyclerViewFragment import code.name.monkey.retromusic.util.PreferenceUtil +import java.util.* class GenreFragment : AbsLibraryPagerRecyclerViewFragment(), GenreContract.GenreView { diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/LibraryFragment.kt b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/LibraryFragment.kt index a9d4b4e0..9ea0ad93 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/LibraryFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/LibraryFragment.kt @@ -7,7 +7,6 @@ import android.widget.TextView import androidx.annotation.StringRes import androidx.appcompat.widget.Toolbar import androidx.fragment.app.Fragment -import butterknife.Unbinder import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.common.ATHToolbarActivity import code.name.monkey.appthemehelper.util.ATHUtil @@ -109,10 +108,7 @@ class LibraryFragment : AbsMainActivityFragment(), CabHolder, MainActivityFragme appbar.addOnOffsetChangedListener(this) mainActivity.title = null mainActivity.setSupportActionBar(toolbar) - - toolbar.setNavigationOnClickListener { NavigationUtil.goToSearch(mainActivity) } - toolbar.setOnClickListener { showMainMenu() } - toolbar.navigationIcon = RetroUtil.getTintedDrawable(mainActivity, R.drawable.ic_search_white_24dp, ThemeStore.textColorPrimary(mainActivity)) + toolbar.navigationIcon = RetroUtil.getTintedDrawable(mainActivity, R.drawable.ic_menu_white_24dp, ThemeStore.textColorPrimary(mainActivity)) } diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/folders/FoldersFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/folders/FoldersFragment.java index 1fd8b121..5ab19f72 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/folders/FoldersFragment.java +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/folders/FoldersFragment.java @@ -40,7 +40,6 @@ import androidx.loader.app.LoaderManager; import androidx.loader.content.Loader; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import butterknife.BindView; import butterknife.ButterKnife; import butterknife.Unbinder; import code.name.monkey.appthemehelper.ThemeStore; @@ -74,8 +73,7 @@ public class FoldersFragment extends AbsMainActivityFragment implements AppBarLayout.OnOffsetChangedListener, LoaderManager.LoaderCallbacks> { public static final String TAG = FoldersFragment.class.getSimpleName(); - public static final FileFilter AUDIO_FILE_FILTER = file -> !file.isHidden() && (file.isDirectory() - || + public static final FileFilter AUDIO_FILE_FILTER = file -> !file.isHidden() && (file.isDirectory() || FileUtil.fileIsMimeType(file, "audio/*", MimeTypeMap.getSingleton()) || FileUtil.fileIsMimeType(file, "application/opus", MimeTypeMap.getSingleton()) || FileUtil.fileIsMimeType(file, "application/ogg", MimeTypeMap.getSingleton())); @@ -83,30 +81,18 @@ public class FoldersFragment extends AbsMainActivityFragment implements private static final String PATH = "path"; private static final String CRUMBS = "crumbs"; private static final int LOADER_ID = LoaderIds.Companion.getFOLDERS_FRAGMENT(); - @BindView(R.id.coordinator_layout) - View coordinatorLayout; - @BindView(R.id.container) - View container; + private View coordinatorLayout, container, empty; - @BindView(R.id.title) - TextView title; + private TextView title; - @BindView(android.R.id.empty) - View empty; + private Toolbar toolbar; - @BindView(R.id.toolbar) - Toolbar toolbar; + private BreadCrumbLayout breadCrumbs; - @BindView(R.id.bread_crumbs) - BreadCrumbLayout breadCrumbs; - - @BindView(R.id.app_bar) - AppBarLayout appbar; - - @BindView(R.id.recycler_view) - FastScrollRecyclerView recyclerView; + private AppBarLayout appBarLayout; + private FastScrollRecyclerView recyclerView; private Comparator fileComparator = (lhs, rhs) -> { if (lhs.isDirectory() && !rhs.isDirectory()) { @@ -118,10 +104,8 @@ public class FoldersFragment extends AbsMainActivityFragment implements (rhs.getName()); } }; - private Unbinder unbinder; private SongFileAdapter adapter; private MaterialCab cab; - public FoldersFragment() { } @@ -162,6 +146,17 @@ public class FoldersFragment extends AbsMainActivityFragment implements } } + private void initViews(View view) { + coordinatorLayout = view.findViewById(R.id.coordinatorLayout); + recyclerView = view.findViewById(R.id.recyclerView); + appBarLayout = view.findViewById(R.id.appBarLayout); + breadCrumbs = view.findViewById(R.id.breadCrumbs); + toolbar = view.findViewById(R.id.toolbar); + empty = view.findViewById(android.R.id.empty); + title = view.findViewById(R.id.bannerTitle); + container = view.findViewById(R.id.container); + } + private void setCrumb(BreadCrumbLayout.Crumb crumb, boolean addToHistory) { if (crumb == null) { return; @@ -215,15 +210,13 @@ public class FoldersFragment extends AbsMainActivityFragment implements public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_folder, container, false); - unbinder = ButterKnife.bind(this, view); + initViews(view); return view; } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { setStatusbarColorAuto(view); - //getMainActivity().getSlidingUpPanelLayout().setShadowHeight(0); - setUpAppbarColor(); setUpBreadCrumbs(); setUpRecyclerView(); @@ -238,18 +231,17 @@ public class FoldersFragment extends AbsMainActivityFragment implements int primaryColor = ThemeStore.primaryColor(getContext()); - toolbar.setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp); + toolbar.setNavigationIcon(R.drawable.ic_menu_white_24dp); //noinspection ConstantConditions getActivity().setTitle(null); getMainActivity().setSupportActionBar(toolbar); - toolbar.setNavigationOnClickListener(v -> getActivity().onBackPressed()); TintHelper.setTintAuto(container, primaryColor, true); - appbar.setBackgroundColor(primaryColor); + appBarLayout.setBackgroundColor(primaryColor); toolbar.setBackgroundColor(primaryColor); breadCrumbs.setActivatedContentColor(ToolbarContentTintHelper.toolbarTitleColor(getActivity(), ColorUtil.darkenColor(primaryColor))); breadCrumbs.setDeactivatedContentColor(ToolbarContentTintHelper.toolbarSubtitleColor(getActivity(), ColorUtil.darkenColor(primaryColor))); - appbar.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> getMainActivity().setLightStatusbar(!ATHUtil.isWindowBackgroundDark(getContext()))); + appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> getMainActivity().setLightStatusbar(!ATHUtil.isWindowBackgroundDark(getContext()))); } private void setUpBreadCrumbs() { @@ -261,7 +253,7 @@ public class FoldersFragment extends AbsMainActivityFragment implements ViewUtil.setUpFastScrollRecyclerViewColor(getActivity(), recyclerView, ThemeStore.accentColor(getActivity())); recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); - appbar.addOnOffsetChangedListener(this); + appBarLayout.addOnOffsetChangedListener(this); } private void setUpAdapter() { @@ -286,8 +278,7 @@ public class FoldersFragment extends AbsMainActivityFragment implements @Override public void onDestroyView() { - appbar.removeOnOffsetChangedListener(this); - unbinder.unbind(); + appBarLayout.removeOnOffsetChangedListener(this); super.onDestroyView(); } @@ -473,7 +464,7 @@ public class FoldersFragment extends AbsMainActivityFragment implements @Override public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { container.setPadding(container.getPaddingLeft(), container.getPaddingTop(), - container.getPaddingRight(), appbar.getTotalScrollRange() + verticalOffset); + container.getPaddingRight(), this.appBarLayout.getTotalScrollRange() + verticalOffset); } private void checkIsEmpty() { diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/home/BannerHomeFragment.kt b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/home/BannerHomeFragment.kt index 4b56b5b4..273ab2a9 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/home/BannerHomeFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/home/BannerHomeFragment.kt @@ -1,18 +1,19 @@ package code.name.monkey.retromusic.ui.fragments.mainactivity.home import android.graphics.Bitmap +import android.graphics.Color import android.os.Bundle import android.util.DisplayMetrics import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView +import androidx.appcompat.widget.Toolbar import androidx.core.content.ContextCompat import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import butterknife.OnClick import code.name.monkey.appthemehelper.ThemeStore +import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.retromusic.Constants.USER_BANNER import code.name.monkey.retromusic.Constants.USER_PROFILE import code.name.monkey.retromusic.R @@ -34,80 +35,37 @@ import code.name.monkey.retromusic.util.Compressor import code.name.monkey.retromusic.util.NavigationUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.RetroUtil -import code.name.monkey.retromusic.views.CircularImageView -import code.name.monkey.retromusic.views.MetalRecyclerViewPager import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable import io.reactivex.schedulers.Schedulers -import kotlinx.android.synthetic.main.abs_playlists.* +import kotlinx.android.synthetic.main.fragment_banner_home.* +import kotlinx.android.synthetic.main.home_section_content.* import java.io.File import java.util.* class BannerHomeFragment : AbsMainActivityFragment(), MainActivityFragmentCallbacks, HomeContract.HomeView { + val disposable: CompositeDisposable = CompositeDisposable() + private lateinit var homePresenter: HomePresenter + private lateinit var contentContainerView: View + private lateinit var lastAdded: View + private lateinit var topPlayed: View + private lateinit var actionShuffle: View + private lateinit var history: View + private lateinit var userImage: ImageView + private lateinit var toolbar: Toolbar - override fun onCreateView(inflater: LayoutInflater, viewGroup: ViewGroup?, - savedInstanceState: Bundle?): View? { - val view = inflater.inflate(if (PreferenceUtil.getInstance().isHomeBanner) R.layout.fragment_banner_home else R.layout.fragment_home, viewGroup, false) - - if (!PreferenceUtil.getInstance().isHomeBanner) - setStatusbarColorAuto(view) - - imageView = view.findViewById(R.id.image) - userImage = view.findViewById(R.id.user_image) - recentArtistRV = view.findViewById(R.id.recycler_view) - recentAlbumRV = view.findViewById(R.id.recent_album) - topArtistRV = view.findViewById(R.id.top_artist) - topAlbumRV = view.findViewById(R.id.top_album) - recentArtistContainer = view.findViewById(R.id.recent_artist_container) - recentAlbumsContainer = view.findViewById(R.id.recent_albums_container) - topArtistContainer = view.findViewById(R.id.top_artist_container) - topAlbumContainer = view.findViewById(R.id.top_albums_container) - genresRecyclerView = view.findViewById(R.id.genres) - genreContainer = view.findViewById(R.id.genre_container) - contentContainer = view.findViewById(R.id.content_container) - container = view.findViewById(R.id.container) - suggestionsSongs = view.findViewById(R.id.suggestion_songs) - suggestionsContainer = view.findViewById(R.id.suggestion_container) - - /* lastAdded.setOnClickListener { - NavigationUtil.goToPlaylistNew(activity!!, LastAddedPlaylist(activity!!)) - } - topPlayed.setOnClickListener { - NavigationUtil.goToPlaylistNew(activity!!, MyTopTracksPlaylist(activity!!)) - } - actionShuffle.setOnClickListener { - MusicPlayerRemote.openAndShuffleQueue(SongLoader.getAllSongs(activity!!).blockingFirst(), true) - } - history.setOnClickListener { - NavigationUtil.goToPlaylistNew(activity!!, HistoryPlaylist(activity!!)) - }*/ - userImage.setOnClickListener { - NavigationUtil.goToUserInfo(activity!!) - } - return view + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + mainActivity.setBottomBarVisibility(View.GONE) + } + + override fun onCreateView(inflater: LayoutInflater, viewGroup: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(if (PreferenceUtil.getInstance().isHomeBanner) R.layout.fragment_banner_home else R.layout.fragment_home, viewGroup, false) } - private var imageView: ImageView? = null - private lateinit var userImage: CircularImageView - private lateinit var recentAlbumRV: MetalRecyclerViewPager - private lateinit var topAlbumRV: MetalRecyclerViewPager - private lateinit var topArtistRV: RecyclerView - private lateinit var genresRecyclerView: RecyclerView - private lateinit var suggestionsSongs: RecyclerView - private lateinit var recentArtistRV: RecyclerView - private lateinit var recentArtistContainer: View - private lateinit var recentAlbumsContainer: View - private lateinit var topArtistContainer: View - private lateinit var topAlbumContainer: View - private lateinit var genreContainer: View - private lateinit var container: View - private lateinit var contentContainer: View - private lateinit var suggestionsContainer: View - private lateinit var homePresenter: HomePresenter - val disposable: CompositeDisposable = CompositeDisposable() private val displayMetrics: DisplayMetrics get() { @@ -122,16 +80,12 @@ class BannerHomeFragment : AbsMainActivityFragment(), MainActivityFragmentCallba val timeOfDay = c.get(Calendar.HOUR_OF_DAY) var images = arrayOf() - if (timeOfDay in 0..5) { - images = resources.getStringArray(R.array.night) - } else if (timeOfDay in 6..11) { - images = resources.getStringArray(R.array.morning) - } else if (timeOfDay in 12..15) { - images = resources.getStringArray(R.array.after_noon) - } else if (timeOfDay in 16..19) { - images = resources.getStringArray(R.array.evening) - } else if (timeOfDay in 20..23) { - images = resources.getStringArray(R.array.night) + when (timeOfDay) { + in 0..5 -> images = resources.getStringArray(R.array.night) + in 6..11 -> images = resources.getStringArray(R.array.morning) + in 12..15 -> images = resources.getStringArray(R.array.after_noon) + in 16..19 -> images = resources.getStringArray(R.array.evening) + in 20..23 -> images = resources.getStringArray(R.array.night) } val day = images[Random().nextInt(images.size)] @@ -141,47 +95,39 @@ class BannerHomeFragment : AbsMainActivityFragment(), MainActivityFragmentCallba private fun loadTimeImage(day: String) { - if (imageView != null) { + if (bannerImage != null) { if (PreferenceUtil.getInstance().bannerImage.isEmpty()) { Glide.with(activity).load(day) .asBitmap() .placeholder(R.drawable.material_design_default) .diskCacheStrategy(DiskCacheStrategy.SOURCE) - .into(imageView!!) + .into(bannerImage!!) } else { - loadBannerFromStorage() + + disposable.add(Compressor(context!!) + .setQuality(100) + .setCompressFormat(Bitmap.CompressFormat.WEBP) + .compressToBitmapAsFlowable(File(PreferenceUtil.getInstance().bannerImage, USER_BANNER)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { bannerImage!!.setImageBitmap(it) }) } } } - private fun loadBannerFromStorage() { - - disposable.add(Compressor(context!!) - .setQuality(100) - .setCompressFormat(Bitmap.CompressFormat.WEBP) - .compressToBitmapAsFlowable( - File(PreferenceUtil.getInstance().bannerImage, USER_BANNER)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { imageView!!.setImageBitmap(it) }) - } - private fun loadImageFromStorage(imageView: ImageView) { - disposable.add(Compressor(context!!) .setMaxHeight(300) .setMaxWidth(300) .setQuality(75) .setCompressFormat(Bitmap.CompressFormat.WEBP) - .compressToBitmapAsFlowable( - File(PreferenceUtil.getInstance().profileImage, USER_PROFILE)) + .compressToBitmapAsFlowable(File(PreferenceUtil.getInstance().profileImage, USER_PROFILE)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ imageView.setImageBitmap(it) }) { - imageView.setImageDrawable(ContextCompat - .getDrawable(context!!, R.drawable.ic_person_flat)) + imageView.setImageDrawable(ContextCompat.getDrawable(context!!, R.drawable.ic_person_flat)) }) } @@ -193,20 +139,55 @@ class BannerHomeFragment : AbsMainActivityFragment(), MainActivityFragmentCallba override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + mainActivity.setBottomBarVisibility(View.GONE) + + toolbar = view.findViewById(R.id.toolbar) + + if (!PreferenceUtil.getInstance().isHomeBanner) + setStatusbarColorAuto(view) + + lastAdded = view.findViewById(R.id.lastAdded) + lastAdded.setOnClickListener { + NavigationUtil.goToPlaylistNew(activity!!, LastAddedPlaylist(activity!!)) + } + + topPlayed = view.findViewById(R.id.topPlayed) + topPlayed.setOnClickListener { + NavigationUtil.goToPlaylistNew(activity!!, MyTopTracksPlaylist(activity!!)) + } + + actionShuffle = view.findViewById(R.id.actionShuffle) + actionShuffle.setOnClickListener { + MusicPlayerRemote.openAndShuffleQueue(SongLoader.getAllSongs(activity!!).blockingFirst(), true) + } + + history = view.findViewById(R.id.history) + history.setOnClickListener { + NavigationUtil.goToPlaylistNew(activity!!, HistoryPlaylist(activity!!)) + } + + userImage = view.findViewById(R.id.userImage) + userImage.setOnClickListener { showMainMenu() } + + homePresenter = HomePresenter(this) + + contentContainerView = view.findViewById(R.id.contentContainer) + contentContainerView.setBackgroundColor(ThemeStore.primaryColor(context!!)) + + //bannerTitle.setTextColor(ThemeStore.textColorPrimary(context!!)) + setupToolbar() - loadImageFromStorage(userImage) homePresenter.subscribe() + + loadImageFromStorage(userImage) getTimeOfTheDay() } private fun setupToolbar() { - userImage.setOnClickListener { showMainMenu() } - contentContainer.setBackgroundColor(ThemeStore.primaryColor(mainActivity)) - } - - @OnClick(R.id.searchIcon) - internal fun search() { - NavigationUtil.goToSearch(mainActivity) + toolbar.navigationIcon = TintHelper.createTintedDrawable(ContextCompat.getDrawable(context!!, R.drawable.ic_menu_white_24dp), ThemeStore.textColorPrimary(context!!)) + mainActivity.title = null + mainActivity.setSupportActionBar(toolbar) + toolbar.setBackgroundColor(Color.TRANSPARENT) } override fun handleBackPress(): Boolean { @@ -237,41 +218,46 @@ class BannerHomeFragment : AbsMainActivityFragment(), MainActivityFragmentCallba override fun recentArtist(artists: ArrayList) { recentArtistContainer.visibility = View.VISIBLE - recentArtistRV.layoutManager = GridLayoutManager(mainActivity, 1, GridLayoutManager.HORIZONTAL, false) - val artistAdapter = ArtistAdapter(mainActivity, artists, PreferenceUtil.getInstance().getHomeGridStyle(context!!), false, null) - recentArtistRV.adapter = artistAdapter + recentArtist.apply { + val artistAdapter = ArtistAdapter(mainActivity, artists, PreferenceUtil.getInstance().getHomeGridStyle(context!!), false, null) + layoutManager = GridLayoutManager(mainActivity, 1, GridLayoutManager.HORIZONTAL, false) + adapter = artistAdapter + } } override fun recentAlbum(albums: ArrayList) { recentAlbumsContainer.visibility = View.VISIBLE - val artistAdapter = AlbumFullWithAdapter(mainActivity, - displayMetrics) + val artistAdapter = AlbumFullWithAdapter(mainActivity, displayMetrics) artistAdapter.swapData(albums) - recentAlbumRV.adapter = artistAdapter + recentAlbum.adapter = artistAdapter } override fun topArtists(artists: ArrayList) { topArtistContainer.visibility = View.VISIBLE - topArtistRV.layoutManager = GridLayoutManager(mainActivity, 1, GridLayoutManager.HORIZONTAL, false) - val artistAdapter = ArtistAdapter(mainActivity, artists, PreferenceUtil.getInstance().getHomeGridStyle(context!!), false, null) - topArtistRV.adapter = artistAdapter + topArtist.apply { + layoutManager = GridLayoutManager(mainActivity, 1, GridLayoutManager.HORIZONTAL, false) + val artistAdapter = ArtistAdapter(mainActivity, artists, PreferenceUtil.getInstance().getHomeGridStyle(context!!), false, null) + adapter = artistAdapter + } } override fun topAlbums(albums: ArrayList) { - topAlbumContainer.visibility = View.VISIBLE + topAlbumsContainer.visibility = View.VISIBLE val artistAdapter = AlbumFullWithAdapter(mainActivity, displayMetrics) artistAdapter.swapData(albums) - topAlbumRV.adapter = artistAdapter + topAlbum.adapter = artistAdapter } override fun suggestions(songs: ArrayList) { if (!songs.isEmpty()) { - suggestionsContainer.visibility = View.VISIBLE + suggestionContainer.visibility = View.VISIBLE val artistAdapter = CollageSongAdapter(mainActivity, songs) - suggestionsSongs.layoutManager = if (RetroUtil.isTablet()) GridLayoutManager(mainActivity, 2) else LinearLayoutManager(mainActivity) - suggestionsSongs.adapter = artistAdapter + suggestionSongs.apply { + layoutManager = if (RetroUtil.isTablet()) GridLayoutManager(mainActivity, 2) else LinearLayoutManager(mainActivity) + adapter = artistAdapter + } } } @@ -281,10 +267,11 @@ class BannerHomeFragment : AbsMainActivityFragment(), MainActivityFragmentCallba override fun geners(genres: ArrayList) { genreContainer.visibility = View.VISIBLE - genresRecyclerView.layoutManager = LinearLayoutManager(context) - - val genreAdapter = GenreAdapter(activity!!, genres, R.layout.item_list) - genresRecyclerView.adapter = genreAdapter + genresRecyclerView.apply { + val genreAdapter = GenreAdapter(activity!!, genres, R.layout.item_list) + layoutManager = LinearLayoutManager(context) + adapter = genreAdapter + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/PlayerAlbumCoverFragment.kt b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/PlayerAlbumCoverFragment.kt index 6739497f..008fa552 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/PlayerAlbumCoverFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/PlayerAlbumCoverFragment.kt @@ -5,7 +5,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.viewpager.widget.ViewPager -import butterknife.Unbinder import code.name.monkey.retromusic.R import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.transform.ParallaxPagerTransformer diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/adaptive/AdaptiveFragment.kt b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/adaptive/AdaptiveFragment.kt index eaa45107..5735ebfd 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/adaptive/AdaptiveFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/adaptive/AdaptiveFragment.kt @@ -4,6 +4,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.appcompat.widget.Toolbar import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper @@ -12,18 +13,22 @@ import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.ui.fragments.player.PlayerAlbumCoverFragment +import kotlinx.android.synthetic.main.fragment_adaptive_player.* class AdaptiveFragment : AbsPlayerFragment(), PlayerAlbumCoverFragment.Callbacks { + override fun toolbarGet(): Toolbar { + return playerToolbar + } + private var lastColor: Int = 0 - lateinit var playbackControlsFragment: AdaptivePlaybackControlsFragment + private lateinit var playbackControlsFragment: AdaptivePlaybackControlsFragment override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_adaptive_player, container, false); + return inflater.inflate(R.layout.fragment_adaptive_player, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - toolbar = view.findViewById(R.id.toolbar) setUpSubFragments() setUpPlayerToolbar() } @@ -31,19 +36,21 @@ class AdaptiveFragment : AbsPlayerFragment(), PlayerAlbumCoverFragment.Callbacks private fun setUpSubFragments() { playbackControlsFragment = childFragmentManager.findFragmentById(R.id.playback_controls_fragment) as AdaptivePlaybackControlsFragment val playerAlbumCoverFragment = childFragmentManager.findFragmentById(R.id.player_album_cover_fragment) as PlayerAlbumCoverFragment - playerAlbumCoverFragment.setCallbacks(this) - playerAlbumCoverFragment.removeSlideEffect() + playerAlbumCoverFragment.apply { + removeSlideEffect() + }.setCallbacks(this) } private fun setUpPlayerToolbar() { - val primaryColor = ATHUtil.resolveColor(context, R.attr.iconColor) - /*toolbar!!.apply { + ATHUtil.resolveColor(context, R.attr.iconColor) + val primaryColor = ThemeStore.primaryColor(context!!) + playerToolbar.apply { inflateMenu(R.menu.menu_player) setNavigationOnClickListener { activity!!.onBackPressed() } ToolbarContentTintHelper.colorizeToolbar(this, primaryColor, activity) setTitleTextColor(primaryColor) setSubtitleTextColor(ThemeStore.textColorSecondary(context!!)) - }.setOnMenuItemClickListener(this)*/ + }.setOnMenuItemClickListener(this) } override fun onServiceConnected() { @@ -59,10 +66,10 @@ class AdaptiveFragment : AbsPlayerFragment(), PlayerAlbumCoverFragment.Callbacks private fun updateSong() { val song = MusicPlayerRemote.currentSong - /*toolbar!!.apply { + playerToolbar.apply { title = song.title subtitle = song.artistName - }*/ + } } override fun toggleFavorite(song: Song) { @@ -80,7 +87,7 @@ class AdaptiveFragment : AbsPlayerFragment(), PlayerAlbumCoverFragment.Callbacks playbackControlsFragment.setDark(color) lastColor = color callbacks!!.onPaletteColorChanged() - //ToolbarContentTintHelper.colorizeToolbar(toolbar, ATHUtil.resolveColor(context, R.attr.iconColor), activity) + ToolbarContentTintHelper.colorizeToolbar(playerToolbar, ATHUtil.resolveColor(context, R.attr.iconColor), activity) } override fun onShow() { diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/adaptive/AdaptivePlaybackControlsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/adaptive/AdaptivePlaybackControlsFragment.kt index 4ef44759..e607b269 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/adaptive/AdaptivePlaybackControlsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/adaptive/AdaptivePlaybackControlsFragment.kt @@ -21,11 +21,10 @@ import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerControlsFragment import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.PreferenceUtil -import code.name.monkey.retromusic.views.PlayPauseDrawable +import kotlinx.android.synthetic.main.fragment_adaptive_player_playback_controls.* class AdaptivePlaybackControlsFragment : AbsPlayerControlsFragment() { - private val playPauseDrawable: PlayPauseDrawable? = null private var lastPlaybackControlsColor: Int = 0 private var lastDisabledPlaybackControlsColor: Int = 0 private var progressViewUpdateHelper: MusicProgressViewUpdateHelper? = null @@ -45,13 +44,13 @@ class AdaptivePlaybackControlsFragment : AbsPlayerControlsFragment() { setUpMusicControllers() hideVolumeIfAvailable() - playPauseFab!!.setOnClickListener { + playPauseButton.setOnClickListener { if (MusicPlayerRemote.isPlaying) { MusicPlayerRemote.pauseSong() } else { MusicPlayerRemote.resumePlaying() } - showBouceAnimation(playPauseFab!!) + showBouceAnimation(playPauseButton) } } @@ -97,9 +96,9 @@ class AdaptivePlaybackControlsFragment : AbsPlayerControlsFragment() { updatePrevNextColor() updatePlayPauseColor() - TintHelper.setTintAuto(playPauseFab!!, MaterialValueHelper.getPrimaryTextColor(context, ColorUtil.isColorLight(color)), false) - TintHelper.setTintAuto(playPauseFab!!, color, true) - TintHelper.setTintAuto(progressSlider!!, color, false) + TintHelper.setTintAuto(playPauseButton, MaterialValueHelper.getPrimaryTextColor(context, ColorUtil.isColorLight(color)), false) + TintHelper.setTintAuto(playPauseButton, color, true) + TintHelper.setTintAuto(progressSlider, color, false) } private fun updatePlayPauseColor() { @@ -107,18 +106,17 @@ class AdaptivePlaybackControlsFragment : AbsPlayerControlsFragment() { } private fun setUpPlayPauseFab() { - playPauseFab!!.setOnClickListener(PlayPauseButtonOnClickHandler()) + playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) } - protected fun updatePlayPauseDrawableState() { + private fun updatePlayPauseDrawableState() { if (MusicPlayerRemote.isPlaying) { - playPauseFab!!.setImageResource(R.drawable.ic_pause_white_24dp) + playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp) } else { - playPauseFab!!.setImageResource(R.drawable.ic_play_arrow_white_24dp) + playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp) } } - private fun setUpMusicControllers() { setUpPlayPauseFab() setUpPrevNext() @@ -129,47 +127,62 @@ class AdaptivePlaybackControlsFragment : AbsPlayerControlsFragment() { private fun setUpPrevNext() { updatePrevNextColor() - nextButton!!.setOnClickListener { MusicPlayerRemote.playNextSong() } - prevButton!!.setOnClickListener { MusicPlayerRemote.back() } + nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } + previousButton.setOnClickListener { MusicPlayerRemote.back() } } private fun updatePrevNextColor() { - nextButton!!.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) - prevButton!!.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) } private fun setUpShuffleButton() { - shuffleButton!!.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } + shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } } override fun updateShuffleState() { when (MusicPlayerRemote.shuffleMode) { - MusicService.SHUFFLE_MODE_SHUFFLE -> shuffleButton!!.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) - else -> shuffleButton!!.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + MusicService.SHUFFLE_MODE_SHUFFLE -> shuffleButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + else -> shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) } } private fun setUpRepeatButton() { - repeatButton!!.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } + repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } } override fun updateRepeatState() { when (MusicPlayerRemote.repeatMode) { MusicService.REPEAT_MODE_NONE -> { - repeatButton!!.setImageResource(R.drawable.ic_repeat_white_24dp) - repeatButton!!.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp) + repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) } MusicService.REPEAT_MODE_ALL -> { - repeatButton!!.setImageResource(R.drawable.ic_repeat_white_24dp) - repeatButton!!.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp) + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) } MusicService.REPEAT_MODE_THIS -> { - repeatButton!!.setImageResource(R.drawable.ic_repeat_one_white_24dp) - repeatButton!!.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + repeatButton.setImageResource(R.drawable.ic_repeat_one_white_24dp) + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) } } } + override fun onUpdateProgressViews(progress: Int, total: Int) { + progressSlider.max = total + + val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) + animator.duration = 1500 + animator.interpolator = LinearInterpolator() + animator.start() + + songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) + songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + } + + private fun hideVolumeIfAvailable() { + volumeFragmentContainer.visibility = if (PreferenceUtil.getInstance().volumeToggle) View.VISIBLE else View.GONE + } public override fun show() { //Ignore @@ -180,7 +193,7 @@ class AdaptivePlaybackControlsFragment : AbsPlayerControlsFragment() { } override fun setUpProgressSlider() { - progressSlider!!.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { + progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) { MusicPlayerRemote.seekTo(progress) @@ -191,19 +204,5 @@ class AdaptivePlaybackControlsFragment : AbsPlayerControlsFragment() { }) } - override fun onUpdateProgressViews(progress: Int, total: Int) { - progressSlider!!.max = total - val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) - animator.duration = 1500 - animator.interpolator = LinearInterpolator() - animator.start() - - songTotalTime!!.text = MusicUtil.getReadableDurationString(total.toLong()) - songCurrentProgress!!.text = MusicUtil.getReadableDurationString(progress.toLong()) - } - - private fun hideVolumeIfAvailable() { - volumeContainer!!.visibility = if (PreferenceUtil.getInstance().volumeToggle) View.VISIBLE else View.GONE - } } diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/blur/BlurPlaybackControlsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/blur/BlurPlaybackControlsFragment.kt index f643ad47..90312741 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/blur/BlurPlaybackControlsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/blur/BlurPlaybackControlsFragment.kt @@ -3,19 +3,23 @@ package code.name.monkey.retromusic.ui.fragments.player.blur import android.animation.ObjectAnimator import android.graphics.Color import android.graphics.PorterDuff +import android.graphics.drawable.ClipDrawable +import android.graphics.drawable.LayerDrawable import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.animation.AccelerateInterpolator import android.view.animation.DecelerateInterpolator import android.view.animation.LinearInterpolator import android.widget.SeekBar import android.widget.TextView import androidx.appcompat.widget.AppCompatTextView import androidx.core.content.ContextCompat -import butterknife.BindView -import butterknife.ButterKnife -import butterknife.Unbinder +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.TintHelper import code.name.monkey.retromusic.R import code.name.monkey.retromusic.helper.MusicPlayerRemote @@ -27,13 +31,14 @@ import code.name.monkey.retromusic.ui.fragments.VolumeFragment import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerControlsFragment import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.PreferenceUtil +import kotlinx.android.synthetic.main.fragment_player_playback_controls.* +import kotlinx.android.synthetic.main.media_button.* +import kotlinx.android.synthetic.main.player_time.* +import kotlinx.android.synthetic.main.volume_controls.* class BlurPlaybackControlsFragment : AbsPlayerControlsFragment() { - lateinit var songTitle: AppCompatTextView - lateinit var text: TextView - private var lastPlaybackControlsColor: Int = 0 private var lastDisabledPlaybackControlsColor: Int = 0 private var progressViewUpdateHelper: MusicProgressViewUpdateHelper? = null @@ -43,27 +48,35 @@ class BlurPlaybackControlsFragment : AbsPlayerControlsFragment() { progressViewUpdateHelper = MusicProgressViewUpdateHelper(this) } - override fun onCreateView(inflater: LayoutInflater, - container: ViewGroup?, + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - val view = inflater.inflate(R.layout.fragment_blur_playback_controls, container, false) - songTitle = view.findViewById(R.id.title) - text = view.findViewById(R.id.text) - return view + + return inflater.inflate(R.layout.fragment_player_playback_controls, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setUpMusicControllers() - volumeContainer!!.visibility = if (PreferenceUtil.getInstance().volumeToggle) View.VISIBLE else View.GONE - val mVolumeFragment = childFragmentManager.findFragmentById(R.id.volume_fragment) as VolumeFragment - mVolumeFragment.tintWhiteColor() + if (PreferenceUtil.getInstance().volumeToggle) { + volumeFragmentContainer.visibility = View.VISIBLE + } else { + volumeFragmentContainer.visibility = View.GONE + } + + playPauseButton.setOnClickListener { + if (MusicPlayerRemote.isPlaying) { + MusicPlayerRemote.pauseSong() + } else { + MusicPlayerRemote.resumePlaying() + } + showBonceAnimation() + } } private fun updateSong() { val song = MusicPlayerRemote.currentSong - songTitle.text = song.title + title.text = song.title text.text = song.artistName } @@ -102,41 +115,50 @@ class BlurPlaybackControlsFragment : AbsPlayerControlsFragment() { } override fun setDark(color: Int) { - lastPlaybackControlsColor = Color.WHITE - lastDisabledPlaybackControlsColor = ContextCompat.getColor(context!!, R.color.md_grey_500) + val colorBg = ATHUtil.resolveColor(activity, android.R.attr.colorBackground) + if (ColorUtil.isColorLight(colorBg)) { + lastPlaybackControlsColor = MaterialValueHelper.getSecondaryTextColor(activity, true) + lastDisabledPlaybackControlsColor = MaterialValueHelper.getSecondaryDisabledTextColor(activity, true) + } else { + lastPlaybackControlsColor = MaterialValueHelper.getPrimaryTextColor(activity, false) + lastDisabledPlaybackControlsColor = MaterialValueHelper.getPrimaryDisabledTextColor(activity, false) + } - songTitle.setTextColor(lastPlaybackControlsColor) - text.setTextColor(lastDisabledPlaybackControlsColor) - - setProgressBarColor() - - songCurrentProgress!!.setTextColor(lastPlaybackControlsColor) - songTotalTime!!.setTextColor(lastPlaybackControlsColor) + if (PreferenceUtil.getInstance().adaptiveColor) { + setFabColor(color) + } else { + setFabColor(ThemeStore.accentColor(context!!)) + } updateRepeatState() updateShuffleState() updatePrevNextColor() } - private fun setProgressBarColor() { - TintHelper.setTintAuto(progressSlider!!, Color.WHITE, false) + private fun setFabColor(i: Int) { + TintHelper.setTintAuto(playPauseButton, MaterialValueHelper.getPrimaryTextColor(context, ColorUtil.isColorLight(i)), false) + TintHelper.setTintAuto(playPauseButton, i, true) + setProgressBarColor(i) + } + + private fun setProgressBarColor(newColor: Int) { + val ld = progressSlider.progressDrawable as LayerDrawable + val clipDrawable = ld.findDrawableByLayerId(android.R.id.progress) as ClipDrawable + clipDrawable.setColorFilter(newColor, PorterDuff.Mode.SRC_IN) } private fun setUpPlayPauseFab() { - TintHelper.setTintAuto(playPauseFab!!, Color.WHITE, true) - TintHelper.setTintAuto(playPauseFab!!, Color.BLACK, false) - playPauseFab!!.setOnClickListener(PlayPauseButtonOnClickHandler()) + playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) } - protected fun updatePlayPauseDrawableState() { + private fun updatePlayPauseDrawableState() { if (MusicPlayerRemote.isPlaying) { - playPauseFab!!.setImageResource(R.drawable.ic_pause_white_24dp) + playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp) } else { - playPauseFab!!.setImageResource(R.drawable.ic_play_arrow_white_24dp) + playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp) } } - private fun setUpMusicControllers() { setUpPlayPauseFab() setUpPrevNext() @@ -147,50 +169,49 @@ class BlurPlaybackControlsFragment : AbsPlayerControlsFragment() { private fun setUpPrevNext() { updatePrevNextColor() - nextButton!!.setOnClickListener { MusicPlayerRemote.playNextSong() } - prevButton!!.setOnClickListener { MusicPlayerRemote.back() } + nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } + previousButton.setOnClickListener { MusicPlayerRemote.back() } } private fun updatePrevNextColor() { - nextButton!!.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) - prevButton!!.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) } private fun setUpShuffleButton() { - shuffleButton!!.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } + shuffleButton.setOnClickListener { v -> MusicPlayerRemote.toggleShuffleMode() } } override fun updateShuffleState() { when (MusicPlayerRemote.shuffleMode) { - MusicService.SHUFFLE_MODE_SHUFFLE -> shuffleButton!!.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) - else -> shuffleButton!!.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + MusicService.SHUFFLE_MODE_SHUFFLE -> shuffleButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + else -> shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) } } private fun setUpRepeatButton() { - repeatButton!!.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } + repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } } override fun updateRepeatState() { when (MusicPlayerRemote.repeatMode) { MusicService.REPEAT_MODE_NONE -> { - repeatButton!!.setImageResource(R.drawable.ic_repeat_white_24dp) - repeatButton!!.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp) + repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) } MusicService.REPEAT_MODE_ALL -> { - repeatButton!!.setImageResource(R.drawable.ic_repeat_white_24dp) - repeatButton!!.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp) + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) } MusicService.REPEAT_MODE_THIS -> { - repeatButton!!.setImageResource(R.drawable.ic_repeat_one_white_24dp) - repeatButton!!.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + repeatButton.setImageResource(R.drawable.ic_repeat_one_white_24dp) + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) } } } - - override fun show() { - playPauseFab!!.animate() + public override fun show() { + playPauseButton!!.animate() .scaleX(1f) .scaleY(1f) .rotation(360f) @@ -198,34 +219,61 @@ class BlurPlaybackControlsFragment : AbsPlayerControlsFragment() { .start() } - override fun hide() { - if (playPauseFab != null) { - playPauseFab!!.scaleX = 0f - playPauseFab!!.scaleY = 0f - playPauseFab!!.rotation = 0f + public override fun hide() { + if (playPauseButton != null) { + playPauseButton!!.apply { + scaleX = 0f + scaleY = 0f + rotation = 0f + } } } override fun setUpProgressSlider() { - progressSlider!!.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { + progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) { MusicPlayerRemote.seekTo(progress) - onUpdateProgressViews(MusicPlayerRemote.songProgressMillis, MusicPlayerRemote.songDurationMillis) + onUpdateProgressViews(MusicPlayerRemote.songProgressMillis, + MusicPlayerRemote.songDurationMillis) } } }) } + private fun showBonceAnimation() { + playPauseButton.apply { + clearAnimation() + scaleX = 0.9f + scaleY = 0.9f + visibility = View.VISIBLE + pivotX = (width / 2).toFloat() + pivotY = (height / 2).toFloat() + + animate().setDuration(200) + .setInterpolator(DecelerateInterpolator()) + .scaleX(1.1f) + .scaleY(1.1f) + .withEndAction { + animate().setDuration(200) + .setInterpolator(AccelerateInterpolator()) + .scaleX(1f) + .scaleY(1f) + .alpha(1f).start() + }.start() + } + } + + override fun onUpdateProgressViews(progress: Int, total: Int) { - progressSlider!!.max = total + progressSlider.max = total val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) animator.duration = 1500 animator.interpolator = LinearInterpolator() animator.start() - songTotalTime!!.text = MusicUtil.getReadableDurationString(total.toLong()) - songCurrentProgress!!.text = MusicUtil.getReadableDurationString(progress.toLong()) + songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) + songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) } } diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/blur/BlurPlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/blur/BlurPlayerFragment.kt index a29a82c3..52ce65b3 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/blur/BlurPlayerFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/blur/BlurPlayerFragment.kt @@ -6,10 +6,7 @@ import android.preference.PreferenceManager import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.ImageView -import androidx.appcompat.app.AppCompatActivity -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView +import androidx.appcompat.widget.Toolbar import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.retromusic.R import code.name.monkey.retromusic.glide.BlurTransformation @@ -17,20 +14,19 @@ 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.ui.adapter.song.PlayingQueueAdapter import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.ui.fragments.player.PlayerAlbumCoverFragment import com.bumptech.glide.Glide -import com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemAnimator +import kotlinx.android.synthetic.main.fragment_blur.* class BlurPlayerFragment : AbsPlayerFragment() { - lateinit var playbackControlsFragment: BlurPlaybackControlsFragment + override fun toolbarGet(): Toolbar { + return playerToolbar + } + + private lateinit var playbackControlsFragment: BlurPlaybackControlsFragment - private var colorBackground: ImageView? = null private var lastColor: Int = 0 - private var playingQueueAdapter: PlayingQueueAdapter? = null - private var layoutManager: LinearLayoutManager? = null - private var recyclerView: RecyclerView? = null override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, @@ -51,7 +47,7 @@ class BlurPlayerFragment : AbsPlayerFragment() { } private fun setUpPlayerToolbar() { - toolbar!!.apply { + playerToolbar!!.apply { inflateMenu(R.menu.menu_player) setNavigationOnClickListener { activity!!.onBackPressed() } ToolbarContentTintHelper.colorizeToolbar(this, Color.WHITE, activity) @@ -66,7 +62,7 @@ class BlurPlayerFragment : AbsPlayerFragment() { playbackControlsFragment.setDark(color) lastColor = color callbacks!!.onPaletteColorChanged() - ToolbarContentTintHelper.colorizeToolbar(toolbar!!, Color.WHITE, activity) + ToolbarContentTintHelper.colorizeToolbar(playerToolbar!!, Color.WHITE, activity) } override fun toggleFavorite(song: Song) { @@ -95,15 +91,6 @@ class BlurPlayerFragment : AbsPlayerFragment() { override val paletteColor: Int get() = lastColor - override fun onDestroyView() { - recyclerView!!.apply { - itemAnimator = null - adapter = null - } - playingQueueAdapter = null - layoutManager = null - super.onDestroyView() - } private fun updateBlur() { val activity = activity ?: return @@ -130,69 +117,11 @@ class BlurPlayerFragment : AbsPlayerFragment() { override fun onServiceConnected() { updateIsFavorite() updateBlur() - setUpRecyclerView() } override fun onPlayingMetaChanged() { updateIsFavorite() updateBlur() - updateQueuePosition() - } - - private fun setUpRecyclerView() { - if (recyclerView != null) { - val animator = RefactoredDefaultItemAnimator() - - playingQueueAdapter = PlayingQueueAdapter((activity as AppCompatActivity), - MusicPlayerRemote.playingQueue, - MusicPlayerRemote.position, - R.layout.item_song, - Color.WHITE) - layoutManager = LinearLayoutManager(context) - - recyclerView!!.apply { - layoutManager = layoutManager - adapter = playingQueueAdapter - itemAnimator = animator - } - layoutManager!!.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0) - } - } - - override fun onQueueChanged() { - updateQueue() - updateCurrentSong() - } - - override fun onMediaStoreChanged() { - updateQueue() - updateCurrentSong() - } - - private fun updateCurrentSong() {} - - private fun updateQueuePosition() { - - playingQueueAdapter!!.apply { - setCurrent(MusicPlayerRemote.position) - resetToCurrentPosition() - } - - } - - private fun updateQueue() { - playingQueueAdapter!!.apply { - swapDataSet(MusicPlayerRemote.playingQueue, MusicPlayerRemote.position) - resetToCurrentPosition() - } - - } - - private fun resetToCurrentPosition() { - recyclerView!!.apply { - stopScroll() - } - layoutManager!!.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0) } } diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/cardblur/CardBlurFragment.kt b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/cardblur/CardBlurFragment.kt new file mode 100644 index 00000000..b5347a69 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/cardblur/CardBlurFragment.kt @@ -0,0 +1,151 @@ +package code.name.monkey.retromusic.ui.fragments.player.cardblur + +import android.graphics.Color +import android.os.Bundle +import android.preference.PreferenceManager +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.widget.Toolbar +import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper +import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.glide.BlurTransformation +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.ui.fragments.base.AbsPlayerFragment +import code.name.monkey.retromusic.ui.fragments.player.PlayerAlbumCoverFragment +import code.name.monkey.retromusic.ui.fragments.player.normal.PlayerFragment +import com.bumptech.glide.Glide +import kotlinx.android.synthetic.main.fragment_card_blur_player.* + +class CardBlurFragment : AbsPlayerFragment(), PlayerAlbumCoverFragment.Callbacks { + override fun toolbarGet(): Toolbar { + return playerToolbar + } + + private var lastColor: Int = 0 + override val paletteColor: Int + get() = lastColor + private lateinit var playbackControlsFragment: CardBlurPlaybackControlsFragment + + + override fun onShow() { + playbackControlsFragment.show() + } + + override fun onHide() { + playbackControlsFragment.hide() + onBackPressed() + } + + override fun onBackPressed(): Boolean { + return false + } + + override fun toolbarIconColor(): Int { + return Color.WHITE + } + + override fun onColorChanged(color: Int) { + playbackControlsFragment.setDark(color) + lastColor = color + callbacks!!.onPaletteColorChanged() + ToolbarContentTintHelper.colorizeToolbar(playerToolbar, Color.WHITE, activity) + + playerToolbar.setTitleTextColor(Color.WHITE) + playerToolbar.setSubtitleTextColor(Color.WHITE) + } + + override fun toggleFavorite(song: Song) { + super.toggleFavorite(song) + if (song.id == MusicPlayerRemote.currentSong.id) { + updateIsFavorite() + } + } + + override fun onFavoriteToggled() { + toggleFavorite(MusicPlayerRemote.currentSong) + } + + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + + return inflater.inflate(R.layout.fragment_card_blur_player, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setUpSubFragments() + setUpPlayerToolbar() + } + + private fun setUpSubFragments() { + playbackControlsFragment = childFragmentManager.findFragmentById(R.id.playbackControlsFragment) as CardBlurPlaybackControlsFragment + val playerAlbumCoverFragment = childFragmentManager.findFragmentById(R.id.playerAlbumCoverFragment) as PlayerAlbumCoverFragment? + if (playerAlbumCoverFragment != null) { + playerAlbumCoverFragment.setCallbacks(this) + playerAlbumCoverFragment.removeEffect() + } + + } + + private fun setUpPlayerToolbar() { + playerToolbar.apply { + inflateMenu(R.menu.menu_player) + setNavigationOnClickListener { activity!!.onBackPressed() } + setTitleTextColor(Color.WHITE) + setSubtitleTextColor(Color.WHITE) + ToolbarContentTintHelper.colorizeToolbar(playerToolbar, Color.WHITE, activity) + }.setOnMenuItemClickListener(this) + } + + override fun onServiceConnected() { + updateIsFavorite() + updateBlur() + updateSong() + } + + override fun onPlayingMetaChanged() { + updateIsFavorite() + updateBlur() + updateSong() + } + + private fun updateSong() { + val song = MusicPlayerRemote.currentSong + playerToolbar.apply { + title = song.title + subtitle = song.artistName + } + } + + private fun updateBlur() { + val activity = activity ?: return + val blurAmount = PreferenceManager.getDefaultSharedPreferences(context) + .getInt("new_blur_amount", 25) + + colorBackground!!.clearColorFilter() + SongGlideRequest.Builder.from(Glide.with(activity), MusicPlayerRemote.currentSong) + .checkIgnoreMediaStore(activity) + .generatePalette(activity) + .build() + .transform(BlurTransformation.Builder(getActivity()!!).blurRadius(blurAmount.toFloat()).build()) + .into(object : RetroMusicColoredTarget(colorBackground!!) { + override fun onColorReady(color: Int) { + if (color == defaultFooterColor) { + colorBackground!!.setColorFilter(color) + } + } + }) + } + + companion object { + + fun newInstance(): PlayerFragment { + return PlayerFragment() + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/cardblur/CardBlurPlaybackControlsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/cardblur/CardBlurPlaybackControlsFragment.kt new file mode 100644 index 00000000..f5602749 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/cardblur/CardBlurPlaybackControlsFragment.kt @@ -0,0 +1,222 @@ +package code.name.monkey.retromusic.ui.fragments.player.cardblur + +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.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.MaterialValueHelper +import code.name.monkey.appthemehelper.util.TintHelper +import code.name.monkey.retromusic.R +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.service.MusicService +import code.name.monkey.retromusic.ui.fragments.VolumeFragment +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerControlsFragment +import code.name.monkey.retromusic.util.MusicUtil +import code.name.monkey.retromusic.util.PreferenceUtil +import kotlinx.android.synthetic.main.fragment_card_blur_player_playback_controls.* +import kotlinx.android.synthetic.main.media_button.* +import kotlinx.android.synthetic.main.player_time.* +import kotlinx.android.synthetic.main.volume_controls.* + +class CardBlurPlaybackControlsFragment : AbsPlayerControlsFragment() { + + private var lastPlaybackControlsColor: Int = 0 + private var lastDisabledPlaybackControlsColor: Int = 0 + private var progressViewUpdateHelper: MusicProgressViewUpdateHelper? = null + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + progressViewUpdateHelper = MusicProgressViewUpdateHelper(this) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + + return inflater.inflate(R.layout.fragment_card_blur_player_playback_controls, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setUpMusicControllers() + setupVolumeControls() + hideVolumeIfAvailable() + } + + private fun hideVolumeIfAvailable() { + volumeFragmentContainer.visibility = if (PreferenceUtil.getInstance().volumeToggle) View.VISIBLE else View.GONE + } + + override fun setDark(color: Int) { + lastPlaybackControlsColor = Color.WHITE + lastDisabledPlaybackControlsColor = ColorUtil.withAlpha(Color.WHITE, 0.3f) + + updateRepeatState() + updateShuffleState() + updatePrevNextColor() + updateProgressTextColor() + } + + + private fun setUpPlayPauseFab() { + playPauseButton.apply { + TintHelper.setTintAuto(this, Color.WHITE, true) + TintHelper.setTintAuto(this, Color.BLACK, false) + setOnClickListener(PlayPauseButtonOnClickHandler()) + } + + } + + private fun updatePlayPauseDrawableState() { + when { + MusicPlayerRemote.isPlaying -> playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp) + else -> playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp) + } + } + + private fun setupVolumeControls() { + val volumeFragment = childFragmentManager.findFragmentById(R.id.volumeFragment) as VolumeFragment + volumeFragment.tintWhiteColor() + } + + private fun updateProgressTextColor() { + val color = MaterialValueHelper.getPrimaryTextColor(context, false) + songTotalTime.setTextColor(color) + songCurrentProgress.setTextColor(color) + } + + + override fun onResume() { + super.onResume() + progressViewUpdateHelper!!.start() + } + + override fun onPause() { + super.onPause() + progressViewUpdateHelper!!.stop() + } + + override fun onServiceConnected() { + updatePlayPauseDrawableState() + updateRepeatState() + updateShuffleState() + } + + override fun onPlayStateChanged() { + updatePlayPauseDrawableState() + } + + override fun onRepeatModeChanged() { + updateRepeatState() + } + + override fun onShuffleModeChanged() { + updateShuffleState() + } + + private fun setUpMusicControllers() { + setUpPlayPauseFab() + setUpPrevNext() + setUpRepeatButton() + setUpShuffleButton() + setUpProgressSlider() + } + + private fun setUpPrevNext() { + updatePrevNextColor() + nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } + previousButton.setOnClickListener { MusicPlayerRemote.back() } + } + + private fun updatePrevNextColor() { + nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + } + + private fun setUpShuffleButton() { + shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } + } + + override fun updateShuffleState() { + when (MusicPlayerRemote.shuffleMode) { + MusicService.SHUFFLE_MODE_SHUFFLE -> shuffleButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + else -> shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + } + } + + private fun setUpRepeatButton() { + repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } + } + + override fun updateRepeatState() { + when (MusicPlayerRemote.repeatMode) { + MusicService.REPEAT_MODE_NONE -> { + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp) + repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + } + MusicService.REPEAT_MODE_ALL -> { + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp) + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + } + MusicService.REPEAT_MODE_THIS -> { + repeatButton.setImageResource(R.drawable.ic_repeat_one_white_24dp) + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + } + } + } + + public override fun show() { + playPauseButton!!.animate() + .scaleX(1f) + .scaleY(1f) + .rotation(360f) + .setInterpolator(DecelerateInterpolator()) + .start() + } + + public override fun hide() { + if (playPauseButton != null) { + playPauseButton!!.apply { + scaleX = 0f + scaleY = 0f + rotation = 0f + } + } + } + + override fun setUpProgressSlider() { + progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { + override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { + if (fromUser) { + MusicPlayerRemote.seekTo(progress) + onUpdateProgressViews(MusicPlayerRemote.songProgressMillis, + MusicPlayerRemote.songDurationMillis) + } + } + }) + } + + + override fun onUpdateProgressViews(progress: Int, total: Int) { + progressSlider.max = total + + val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) + animator.duration = 1500 + animator.interpolator = LinearInterpolator() + animator.start() + + songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) + songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/fit/FitFragment.kt b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/fit/FitFragment.kt new file mode 100644 index 00000000..06543780 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/fit/FitFragment.kt @@ -0,0 +1,110 @@ +package code.name.monkey.retromusic.ui.fragments.player.fit + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +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.helper.MusicPlayerRemote +import code.name.monkey.retromusic.model.Song +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerFragment +import code.name.monkey.retromusic.ui.fragments.player.PlayerAlbumCoverFragment +import kotlinx.android.synthetic.main.fragment_fit.* + + +class FitFragment : AbsPlayerFragment(), PlayerAlbumCoverFragment.Callbacks { + override fun toolbarGet(): Toolbar { + return playerToolbar + } + + private var lastColor: Int = 0 + override val paletteColor: Int + get() = lastColor + + private lateinit var playbackControlsFragment: FitPlaybackControlsFragment + + override fun onShow() { + playbackControlsFragment.show() + } + + override fun onHide() { + playbackControlsFragment.hide() + onBackPressed() + } + + override fun onBackPressed(): Boolean { + return false + } + + override fun toolbarIconColor(): Int { + return ATHUtil.resolveColor(context, R.attr.iconColor) + } + + override fun onColorChanged(color: Int) { + playbackControlsFragment.setDark(color) + lastColor = color + callbacks!!.onPaletteColorChanged() + + ToolbarContentTintHelper.colorizeToolbar(playerToolbar, ATHUtil.resolveColor(context, R.attr.iconColor), activity) + + } + + override fun toggleFavorite(song: Song) { + super.toggleFavorite(song) + if (song.id == MusicPlayerRemote.currentSong.id) { + updateIsFavorite() + } + } + + override fun onFavoriteToggled() { + toggleFavorite(MusicPlayerRemote.currentSong) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + + return inflater.inflate(R.layout.fragment_fit, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setUpSubFragments() + setUpPlayerToolbar() + } + + private fun setUpSubFragments() { + playbackControlsFragment = childFragmentManager.findFragmentById(R.id.playbackControlsFragment) as FitPlaybackControlsFragment + + val playerAlbumCoverFragment = childFragmentManager.findFragmentById(R.id.playerAlbumCoverFragment) as PlayerAlbumCoverFragment + playerAlbumCoverFragment.setCallbacks(this) + playerAlbumCoverFragment.removeEffect() + } + + private fun setUpPlayerToolbar() { + playerToolbar!!.apply { + inflateMenu(R.menu.menu_player) + setNavigationOnClickListener { activity!!.onBackPressed() } + setOnMenuItemClickListener(this@FitFragment) + ToolbarContentTintHelper.colorizeToolbar(this, ATHUtil.resolveColor(context, R.attr.iconColor), activity) + } + } + + override fun onServiceConnected() { + updateIsFavorite() + + } + + override fun onPlayingMetaChanged() { + updateIsFavorite() + } + + companion object { + + fun newInstance(): FitFragment { + return FitFragment() + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/fit/FitPlaybackControlsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/fit/FitPlaybackControlsFragment.kt new file mode 100644 index 00000000..5c508a72 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/fit/FitPlaybackControlsFragment.kt @@ -0,0 +1,271 @@ +package code.name.monkey.retromusic.ui.fragments.player.fit + +import android.animation.ObjectAnimator +import android.graphics.PorterDuff +import android.graphics.drawable.ClipDrawable +import android.graphics.drawable.LayerDrawable +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +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.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.TintHelper +import code.name.monkey.retromusic.R +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.service.MusicService +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerControlsFragment +import code.name.monkey.retromusic.util.MusicUtil +import code.name.monkey.retromusic.util.PreferenceUtil +import kotlinx.android.synthetic.main.fragment_player_playback_controls.* +import kotlinx.android.synthetic.main.media_button.* +import kotlinx.android.synthetic.main.player_time.* +import kotlinx.android.synthetic.main.volume_controls.* + +class FitPlaybackControlsFragment : AbsPlayerControlsFragment() { + + + private var lastPlaybackControlsColor: Int = 0 + private var lastDisabledPlaybackControlsColor: Int = 0 + private var progressViewUpdateHelper: MusicProgressViewUpdateHelper? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + progressViewUpdateHelper = MusicProgressViewUpdateHelper(this) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + + return inflater.inflate(R.layout.fragment_fit_playback_controls, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setUpMusicControllers() + playPauseButton.setOnClickListener { + if (MusicPlayerRemote.isPlaying) { + MusicPlayerRemote.pauseSong() + } else { + MusicPlayerRemote.resumePlaying() + } + showBonceAnimation() + } + } + + private fun updateSong() { + val song = MusicPlayerRemote.currentSong + title.text = song.title + text.text = song.artistName + } + + override fun onResume() { + super.onResume() + progressViewUpdateHelper!!.start() + } + + override fun onPause() { + super.onPause() + progressViewUpdateHelper!!.stop() + } + + override fun onServiceConnected() { + updatePlayPauseDrawableState() + updateRepeatState() + updateShuffleState() + updateSong() + } + + override fun onPlayingMetaChanged() { + super.onPlayingMetaChanged() + updateSong() + } + + override fun onPlayStateChanged() { + updatePlayPauseDrawableState() + } + + override fun onRepeatModeChanged() { + updateRepeatState() + } + + override fun onShuffleModeChanged() { + updateShuffleState() + } + + override fun setDark(color: Int) { + val colorBg = ATHUtil.resolveColor(activity, android.R.attr.colorBackground) + if (ColorUtil.isColorLight(colorBg)) { + lastPlaybackControlsColor = MaterialValueHelper.getSecondaryTextColor(activity, true) + lastDisabledPlaybackControlsColor = MaterialValueHelper.getSecondaryDisabledTextColor(activity, true) + } else { + lastPlaybackControlsColor = MaterialValueHelper.getPrimaryTextColor(activity, false) + lastDisabledPlaybackControlsColor = MaterialValueHelper.getPrimaryDisabledTextColor(activity, false) + } + + if (PreferenceUtil.getInstance().adaptiveColor) { + setFabColor(color) + } else { + setFabColor(ThemeStore.accentColor(context!!)) + } + + updateRepeatState() + updateShuffleState() + updatePrevNextColor() + } + + private fun setFabColor(i: Int) { + TintHelper.setTintAuto(playPauseButton, MaterialValueHelper.getPrimaryTextColor(context, ColorUtil.isColorLight(i)), false) + TintHelper.setTintAuto(playPauseButton, i, true) + + } + + private fun setUpPlayPauseFab() { + playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) + } + + private fun updatePlayPauseDrawableState() { + if (MusicPlayerRemote.isPlaying) { + playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp) + } else { + playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp) + } + } + + private fun setProgressBarColor(newColor: Int) { + val ld = progressSlider!!.progressDrawable as LayerDrawable + val clipDrawable = ld.findDrawableByLayerId(android.R.id.progress) as ClipDrawable + clipDrawable.setColorFilter(newColor, PorterDuff.Mode.SRC_IN) + } + + + private fun setUpMusicControllers() { + setUpPlayPauseFab() + setUpPrevNext() + setUpRepeatButton() + setUpShuffleButton() + setUpProgressSlider() + } + + private fun setUpPrevNext() { + updatePrevNextColor() + nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } + previousButton.setOnClickListener { MusicPlayerRemote.back() } + } + + private fun updatePrevNextColor() { + nextButton!!.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + previousButton!!.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + } + + private fun setUpShuffleButton() { + shuffleButton.setOnClickListener { v -> MusicPlayerRemote.toggleShuffleMode() } + } + + override fun updateShuffleState() { + when (MusicPlayerRemote.shuffleMode) { + MusicService.SHUFFLE_MODE_SHUFFLE -> shuffleButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + else -> shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + } + } + + private fun setUpRepeatButton() { + repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } + } + + override fun updateRepeatState() { + when (MusicPlayerRemote.repeatMode) { + MusicService.REPEAT_MODE_NONE -> { + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp) + repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + } + MusicService.REPEAT_MODE_ALL -> { + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp) + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + } + MusicService.REPEAT_MODE_THIS -> { + repeatButton.setImageResource(R.drawable.ic_repeat_one_white_24dp) + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + } + } + } + + public override fun show() { + playPauseButton!!.animate() + .scaleX(1f) + .scaleY(1f) + .rotation(360f) + .setInterpolator(DecelerateInterpolator()) + .start() + } + + public override fun hide() { + if (playPauseButton != null) { + playPauseButton!!.apply { + scaleX = 0f + scaleY = 0f + rotation = 0f + } + } + } + + override fun setUpProgressSlider() { + progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { + override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { + if (fromUser) { + MusicPlayerRemote.seekTo(progress) + onUpdateProgressViews(MusicPlayerRemote.songProgressMillis, + MusicPlayerRemote.songDurationMillis) + } + } + }) + } + + private fun showBonceAnimation() { + playPauseButton.apply { + clearAnimation() + scaleX = 0.9f + scaleY = 0.9f + visibility = View.VISIBLE + pivotX = (width / 2).toFloat() + pivotY = (height / 2).toFloat() + + animate().setDuration(200) + .setInterpolator(DecelerateInterpolator()) + .scaleX(1.1f) + .scaleY(1.1f) + .withEndAction { + animate().setDuration(200) + .setInterpolator(AccelerateInterpolator()) + .scaleX(1f) + .scaleY(1f) + .alpha(1f).start() + }.start() + } + } + + override fun onUpdateProgressViews(progress: Int, total: Int) { + progressSlider.max = total + + val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) + animator.duration = 1500 + animator.interpolator = LinearInterpolator() + animator.start() + + songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) + songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + } + + private fun hideVolumeIfAvailable() { + volumeFragmentContainer.visibility = if (PreferenceUtil.getInstance().volumeToggle) View.VISIBLE else View.GONE + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/flat/FlatPlaybackControlsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/flat/FlatPlaybackControlsFragment.kt new file mode 100644 index 00000000..bab7b04f --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/flat/FlatPlaybackControlsFragment.kt @@ -0,0 +1,230 @@ +package code.name.monkey.retromusic.ui.fragments.player.flat + +import android.animation.ObjectAnimator +import android.graphics.PorterDuff +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.animation.DecelerateInterpolator +import android.view.animation.LinearInterpolator +import android.widget.SeekBar +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.TintHelper +import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.helper.MusicPlayerRemote +import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper +import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper.Callback +import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler +import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener +import code.name.monkey.retromusic.service.MusicService +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerControlsFragment +import code.name.monkey.retromusic.util.MusicUtil +import code.name.monkey.retromusic.util.PreferenceUtil +import kotlinx.android.synthetic.main.fragment_flat_player_playback_controls.* +import kotlinx.android.synthetic.main.player_time.* +import kotlinx.android.synthetic.main.volume_controls.* + +class FlatPlaybackControlsFragment : AbsPlayerControlsFragment(), Callback { + + private var lastPlaybackControlsColor: Int = 0 + private var lastDisabledPlaybackControlsColor: Int = 0 + private var progressViewUpdateHelper: MusicProgressViewUpdateHelper? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + progressViewUpdateHelper = MusicProgressViewUpdateHelper(this) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_flat_player_playback_controls, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setUpMusicControllers() + hideVolumeIfAvailable() + } + + override fun onResume() { + super.onResume() + progressViewUpdateHelper!!.start() + } + + override fun onPause() { + super.onPause() + progressViewUpdateHelper!!.stop() + } + + override fun onUpdateProgressViews(progress: Int, total: Int) { + progressSlider.max = total + + val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) + animator.duration = 1500 + animator.interpolator = LinearInterpolator() + animator.start() + + songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) + songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) + } + + private fun hideVolumeIfAvailable() { + volumeFragmentContainer.visibility = if (PreferenceUtil.getInstance().volumeToggle) View.VISIBLE else View.GONE + } + + public override fun show() { + playPauseButton!!.animate() + .scaleX(1f) + .scaleY(1f) + .setInterpolator(DecelerateInterpolator()) + .start() + } + + + public override fun hide() { + playPauseButton!!.apply { + scaleX = 0f + scaleY = 0f + rotation = 0f + } + } + + override fun setDark(color: Int) { + val colorBg = ATHUtil.resolveColor(activity, android.R.attr.colorBackground) + val isDark = ColorUtil.isColorLight(colorBg) + if (isDark) { + lastPlaybackControlsColor = MaterialValueHelper.getSecondaryTextColor(activity, true) + lastDisabledPlaybackControlsColor = MaterialValueHelper.getSecondaryDisabledTextColor(activity, true) + } else { + lastPlaybackControlsColor = MaterialValueHelper.getPrimaryTextColor(activity, false) + lastDisabledPlaybackControlsColor = MaterialValueHelper.getPrimaryDisabledTextColor(activity, false) + } + val accentColor = ThemeStore.accentColor(context!!) + val b = PreferenceUtil.getInstance().adaptiveColor + updateTextColors(if (b) color else accentColor) + setProgressBarColor(if (b) color else accentColor) + + + updateRepeatState() + updateShuffleState() + } + + private fun setProgressBarColor(dark: Int) { + TintHelper.setTintAuto(progressSlider!!, dark, false) + } + + private fun updateTextColors(color: Int) { + val isDark = ColorUtil.isColorLight(color) + val darkColor = ColorUtil.darkenColor(color) + val colorPrimary = MaterialValueHelper.getPrimaryTextColor(context, isDark) + val colorSecondary = MaterialValueHelper.getSecondaryTextColor(context, ColorUtil.isColorLight(darkColor)) + + TintHelper.setTintAuto(playPauseButton!!, colorPrimary, false) + TintHelper.setTintAuto(playPauseButton!!, color, true) + + title.setBackgroundColor(color) + title.setTextColor(colorPrimary) + text.setBackgroundColor(darkColor) + text.setTextColor(colorSecondary) + } + + override fun onServiceConnected() { + updatePlayPauseDrawableState() + updateRepeatState() + updateShuffleState() + updateSong() + } + + override fun onPlayingMetaChanged() { + super.onPlayingMetaChanged() + updateSong() + } + + override fun onPlayStateChanged() { + updatePlayPauseDrawableState() + } + + private fun setUpPlayPauseFab() { + playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) + } + + private fun updatePlayPauseDrawableState() { + if (MusicPlayerRemote.isPlaying) { + playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp) + } else { + playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp) + } + } + + private fun setUpMusicControllers() { + setUpPlayPauseFab() + setUpRepeatButton() + setUpShuffleButton() + setUpProgressSlider() + } + + private fun updateSong() { + //TransitionManager.beginDelayedTransition(viewGroup, new ChangeText().setChangeBehavior(ChangeText.CHANGE_BEHAVIOR_OUT_IN)); + val song = MusicPlayerRemote.currentSong + title.text = song.title + text.text = song.artistName + + } + + override fun setUpProgressSlider() { + progressSlider!!.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { + override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { + if (fromUser) { + MusicPlayerRemote.seekTo(progress) + onUpdateProgressViews(MusicPlayerRemote.songProgressMillis, + MusicPlayerRemote.songDurationMillis) + } + } + }) + } + + override fun onRepeatModeChanged() { + updateRepeatState() + } + + override fun onShuffleModeChanged() { + updateShuffleState() + } + + private fun setUpRepeatButton() { + repeatButton.setOnClickListener { v -> MusicPlayerRemote.cycleRepeatMode() } + } + + override fun updateRepeatState() { + when (MusicPlayerRemote.repeatMode) { + MusicService.REPEAT_MODE_NONE -> { + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp) + repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + } + MusicService.REPEAT_MODE_ALL -> { + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp) + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + } + MusicService.REPEAT_MODE_THIS -> { + repeatButton.setImageResource(R.drawable.ic_repeat_one_white_24dp) + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + } + } + } + + private fun setUpShuffleButton() { + shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } + } + + override fun updateShuffleState() { + when (MusicPlayerRemote.shuffleMode) { + MusicService.SHUFFLE_MODE_SHUFFLE -> shuffleButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + else -> shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) + } + } + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/flat/FlatPlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/flat/FlatPlayerFragment.kt new file mode 100644 index 00000000..4ace7ed4 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/flat/FlatPlayerFragment.kt @@ -0,0 +1,128 @@ +package code.name.monkey.retromusic.ui.fragments.player.flat + +import android.animation.ArgbEvaluator +import android.animation.ValueAnimator +import android.graphics.drawable.GradientDrawable +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.widget.Toolbar +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.helper.MusicPlayerRemote +import code.name.monkey.retromusic.model.Song +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerFragment +import code.name.monkey.retromusic.ui.fragments.player.PlayerAlbumCoverFragment +import code.name.monkey.retromusic.util.PreferenceUtil +import code.name.monkey.retromusic.util.ViewUtil +import code.name.monkey.retromusic.views.DrawableGradient +import kotlinx.android.synthetic.main.fragment_flat_player.* + +class FlatPlayerFragment : AbsPlayerFragment(), PlayerAlbumCoverFragment.Callbacks { + override fun toolbarGet(): Toolbar { + return playerToolbar + } + + private var valueAnimator: ValueAnimator? = null + private lateinit var flatPlaybackControlsFragment: FlatPlaybackControlsFragment + private var lastColor: Int = 0 + override val paletteColor: Int + get() = lastColor + + private fun setUpSubFragments() { + flatPlaybackControlsFragment = childFragmentManager.findFragmentById(R.id.playbackControlsFragment) as FlatPlaybackControlsFragment + val playerAlbumCoverFragment = childFragmentManager.findFragmentById(R.id.playerAlbumCoverFragment) as PlayerAlbumCoverFragment + playerAlbumCoverFragment.setCallbacks(this) + } + + private fun setUpPlayerToolbar() { + playerToolbar.inflateMenu(R.menu.menu_player) + playerToolbar.setNavigationOnClickListener { _ -> activity!!.onBackPressed() } + playerToolbar.setOnMenuItemClickListener(this) + ToolbarContentTintHelper.colorizeToolbar(playerToolbar, ATHUtil.resolveColor(context, + R.attr.iconColor), activity) + } + + private fun colorize(i: Int) { + if (valueAnimator != null) { + valueAnimator!!.cancel() + } + + valueAnimator = ValueAnimator.ofObject(ArgbEvaluator(), android.R.color.transparent, i) + valueAnimator!!.addUpdateListener { animation -> + val drawable = DrawableGradient(GradientDrawable.Orientation.TOP_BOTTOM, + intArrayOf(animation.animatedValue as Int, android.R.color.transparent), 0) + colorGradientBackground.background = drawable + + } + valueAnimator!!.setDuration(ViewUtil.RETRO_MUSIC_ANIM_TIME.toLong()).start() + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_flat_player, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setUpPlayerToolbar() + setUpSubFragments() + + } + + override fun onShow() { + flatPlaybackControlsFragment.show() + } + + override fun onHide() { + flatPlaybackControlsFragment.hide() + onBackPressed() + } + + override fun onBackPressed(): Boolean { + return false + } + + override fun toolbarIconColor(): Int { + val isLight = ColorUtil.isColorLight(paletteColor) + return if (PreferenceUtil.getInstance().adaptiveColor) + MaterialValueHelper.getPrimaryTextColor(context, isLight) + else + ATHUtil.resolveColor(context, R.attr.iconColor) + } + + override fun onColorChanged(color: Int) { + lastColor = color + flatPlaybackControlsFragment.setDark(color) + callbacks!!.onPaletteColorChanged() + + val isLight = ColorUtil.isColorLight(color) + + //TransitionManager.beginDelayedTransition(mToolbar); + val iconColor = if (PreferenceUtil.getInstance().adaptiveColor) + MaterialValueHelper.getPrimaryTextColor(context, isLight) + else + ATHUtil.resolveColor(context, R.attr.iconColor) + ToolbarContentTintHelper.colorizeToolbar(playerToolbar, iconColor, activity) + if (PreferenceUtil.getInstance().adaptiveColor) { + colorize(color) + } + } + + + override fun onFavoriteToggled() { + toggleFavorite(MusicPlayerRemote.currentSong) + } + + + override fun toggleFavorite(song: Song) { + super.toggleFavorite(song) + if (song.id == MusicPlayerRemote.currentSong.id) { + updateIsFavorite() + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/MainSettingsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/MainSettingsFragment.java deleted file mode 100644 index 20b352a4..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/MainSettingsFragment.java +++ /dev/null @@ -1,162 +0,0 @@ -package code.name.monkey.retromusic.ui.fragments.settings; - -import android.graphics.Bitmap; -import android.graphics.PorterDuff; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import java.io.File; -import java.util.Calendar; -import java.util.List; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import androidx.appcompat.widget.AppCompatTextView; -import androidx.core.content.ContextCompat; -import androidx.fragment.app.Fragment; -import butterknife.BindView; -import butterknife.BindViews; -import butterknife.ButterKnife; -import butterknife.OnClick; -import butterknife.Unbinder; -import code.name.monkey.appthemehelper.ThemeStore; -import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.ui.activities.SettingsActivity; -import code.name.monkey.retromusic.util.Compressor; -import code.name.monkey.retromusic.util.NavigationUtil; -import code.name.monkey.retromusic.util.PreferenceUtil; -import code.name.monkey.retromusic.views.CircularImageView; -import code.name.monkey.retromusic.views.IconImageView; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.CompositeDisposable; -import io.reactivex.schedulers.Schedulers; - -import static code.name.monkey.retromusic.Constants.USER_PROFILE; - -public class MainSettingsFragment extends Fragment { - - @BindViews({R.id.general_settings_icon, R.id.audio_settings_icon, - R.id.now_playing_settings_icon, R.id.personalize_settings_icon, - R.id.image_settings_icon, R.id.notification_settings_icon, R.id.other_settings_icon}) - List icons; - - @BindView(R.id.container) - ViewGroup container; - @BindView(R.id.user_image_bottom) - CircularImageView userImageBottom; - @BindView(R.id.title_welcome) - AppCompatTextView titleWelcome; - @BindView(R.id.text) - AppCompatTextView text; - private Unbinder unbinder; - private ButterKnife.Action apply = (view, index) -> { - //noinspection ConstantConditions - ((IconImageView) view).setColorFilter(ThemeStore.accentColor(getContext()), PorterDuff.Mode.SRC_IN); - }; - private CompositeDisposable disposable = new CompositeDisposable(); - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - View layout = inflater.inflate(R.layout.fragment_main_settings, container, false); - unbinder = ButterKnife.bind(this, layout); - ButterKnife.apply(icons, apply); - return layout; - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - text.setTextColor(ThemeStore.textColorSecondary(getContext())); - titleWelcome.setTextColor(ThemeStore.textColorPrimary(getContext())); - titleWelcome.setText(String.format("%s %s!", getTimeOfTheDay(), PreferenceUtil.getInstance().getUserName())); - loadImageFromStorage(); - } - - - @Override - public void onDestroyView() { - super.onDestroyView(); - disposable.clear(); - unbinder.unbind(); - } - - @OnClick({R.id.general_settings, R.id.audio_settings, R.id.now_playing_settings, - R.id.user_info_container, R.id.image_settings, R.id.personalize_settings, R.id.notification_settings, R.id.other_settings}) - public void onViewClicked(View view) { - switch (view.getId()) { - case R.id.general_settings: - inflateFragment(new ThemeSettingsFragment(), R.string.general_settings_title); - break; - case R.id.audio_settings: - inflateFragment(new AudioSettings(), R.string.pref_header_audio); - break; - case R.id.user_info_container: - NavigationUtil.goToUserInfo(getActivity()); - break; - case R.id.now_playing_settings: - inflateFragment(new NowPlayingSettingsFragment(), R.string.now_playing); - break; - case R.id.personalize_settings: - inflateFragment(new PersonaizeSettingsFragment(), R.string.personalize); - break; - case R.id.image_settings: - inflateFragment(new ImageSettingFragment(), R.string.pref_header_images); - break; - case R.id.notification_settings: - inflateFragment(new NotificationSettingsFragment(), R.string.notification); - break; - case R.id.other_settings: - inflateFragment(new OtherSettingsFragment(), R.string.others); - break; - } - } - - private void inflateFragment(Fragment fragment, @StringRes int title) { - if (getActivity() != null) { - ((SettingsActivity) getActivity()).setupFragment(fragment, title); - } - } - - private String getTimeOfTheDay() { - String message = getString(R.string.title_good_day); - Calendar c = Calendar.getInstance(); - int timeOfDay = c.get(Calendar.HOUR_OF_DAY); - - if (timeOfDay >= 0 && timeOfDay < 6) { - message = getString(R.string.title_good_night); - } else if (timeOfDay >= 6 && timeOfDay < 12) { - message = getString(R.string.title_good_morning); - } else if (timeOfDay >= 12 && timeOfDay < 16) { - message = getString(R.string.title_good_afternoon); - } else if (timeOfDay >= 16 && timeOfDay < 20) { - message = getString(R.string.title_good_evening); - } else if (timeOfDay >= 20 && timeOfDay < 24) { - message = getString(R.string.title_good_night); - } - return message; - } - - - private void loadImageFromStorage() { - //noinspection ConstantConditions - disposable.add(new Compressor(getContext()) - .setMaxHeight(300) - .setMaxWidth(300) - .setQuality(75) - .setCompressFormat(Bitmap.CompressFormat.WEBP) - .compressToBitmapAsFlowable( - new File(PreferenceUtil.getInstance().getProfileImage(),USER_PROFILE)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(userImageBottom::setImageBitmap, - throwable -> userImageBottom.setImageDrawable(ContextCompat - .getDrawable(getContext(), R.drawable.ic_person_flat)))); - } - -} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/MainSettingsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/MainSettingsFragment.kt new file mode 100644 index 00000000..8bafb141 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/MainSettingsFragment.kt @@ -0,0 +1,54 @@ +package code.name.monkey.retromusic.ui.fragments.settings + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import androidx.annotation.StringRes +import androidx.fragment.app.Fragment +import code.name.monkey.appthemehelper.ThemeStore +import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.ui.activities.SettingsActivity +import kotlinx.android.synthetic.main.fragment_main_settings.* + +class MainSettingsFragment : Fragment(), View.OnClickListener { + override fun onClick(v: View) { + when (v.id) { + R.id.generalSettings -> inflateFragment(ThemeSettingsFragment(), R.string.general_settings_title) + R.id.audioSettings -> inflateFragment(AudioSettings(), R.string.pref_header_audio) + R.id.nowPlayingSettings -> inflateFragment(NowPlayingSettingsFragment(), R.string.now_playing) + R.id.personalizeSettings -> inflateFragment(PersonaizeSettingsFragment(), R.string.personalize) + R.id.imageSettings -> inflateFragment(ImageSettingFragment(), R.string.pref_header_images) + R.id.notificationSettings -> inflateFragment(NotificationSettingsFragment(), R.string.notification) + R.id.otherSettings -> inflateFragment(OtherSettingsFragment(), R.string.others) + } + } + + private val settingsIcons = arrayOf(R.id.general_settings_icon, R.id.audio_settings_icon, R.id.now_playing_settings_icon, R.id.personalize_settings_icon, R.id.image_settings_icon, R.id.notification_settings_icon, R.id.other_settings_icon) + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_main_settings, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + settingsIcons.forEach { + view.findViewById(it).setColorFilter(ThemeStore.accentColor(context!!)) + } + generalSettings.setOnClickListener(this) + audioSettings.setOnClickListener(this) + nowPlayingSettings.setOnClickListener(this) + personalizeSettings.setOnClickListener(this) + imageSettings.setOnClickListener(this) + notificationSettings.setOnClickListener(this) + otherSettings.setOnClickListener(this) + } + + private fun inflateFragment(fragment: Fragment, @StringRes title: Int) { + if (activity != null) { + (activity as SettingsActivity).setupFragment(fragment, title) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/NowPlayingSettingsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/NowPlayingSettingsFragment.java index ee67454f..74da5bfe 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/NowPlayingSettingsFragment.java +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/NowPlayingSettingsFragment.java @@ -7,7 +7,7 @@ import android.view.View; import androidx.annotation.NonNull; import androidx.preference.TwoStatePreference; import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.RetroApplication; +import code.name.monkey.retromusic.App; import code.name.monkey.retromusic.util.PreferenceUtil; /** @@ -25,7 +25,7 @@ public class NowPlayingSettingsFragment extends AbsSettingsFragment implements final TwoStatePreference carouselEffect = (TwoStatePreference) findPreference("carousel_effect"); carouselEffect.setOnPreferenceChangeListener((preference, newValue) -> { - if ((Boolean) newValue && !RetroApplication.Companion.isProVersion()) { + if ((Boolean) newValue && !App.Companion.isProVersion()) { showProToastAndNavigate(getActivity().getString(R.string.pref_title_toggle_carousel_effect)); return false; } diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/PersonaizeSettingsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/PersonaizeSettingsFragment.java index 39dc558d..879c0a1a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/PersonaizeSettingsFragment.java +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/PersonaizeSettingsFragment.java @@ -7,7 +7,7 @@ import android.view.View; import androidx.annotation.NonNull; import androidx.preference.TwoStatePreference; import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.RetroApplication; +import code.name.monkey.retromusic.App; import code.name.monkey.retromusic.util.PreferenceUtil; public class PersonaizeSettingsFragment extends AbsSettingsFragment implements SharedPreferences.OnSharedPreferenceChangeListener { @@ -16,7 +16,7 @@ public class PersonaizeSettingsFragment extends AbsSettingsFragment implements S public void invalidateSettings() { final TwoStatePreference cornerWindow = (TwoStatePreference) findPreference("corner_window"); cornerWindow.setOnPreferenceChangeListener((preference, newValue) -> { - if ((Boolean) newValue && !RetroApplication.Companion.isProVersion()) { + if ((Boolean) newValue && !App.Companion.isProVersion()) { showProToastAndNavigate(getActivity().getString(R.string.pref_title_round_corners)); return false; } diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/ThemeSettingsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/ThemeSettingsFragment.java index 2a4e0095..60e4f8af 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/ThemeSettingsFragment.java +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/ThemeSettingsFragment.java @@ -14,7 +14,7 @@ import code.name.monkey.appthemehelper.common.prefs.supportv7.ATEColorPreference import code.name.monkey.appthemehelper.util.ColorUtil; import code.name.monkey.appthemehelper.util.VersionUtils; import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.RetroApplication; +import code.name.monkey.retromusic.App; import code.name.monkey.retromusic.ui.activities.SettingsActivity; import code.name.monkey.retromusic.util.PreferenceUtil; @@ -48,7 +48,7 @@ public class ThemeSettingsFragment extends AbsSettingsFragment { generalTheme.setOnPreferenceChangeListener((preference, newValue) -> { String theme = (String) newValue; - if (theme.equals("color") && !RetroApplication.Companion.isProVersion()) { + if (theme.equals("color") && !App.Companion.isProVersion()) { primaryColorPref.setVisible(false); showProToastAndNavigate("Color theme"); return false; diff --git a/app/src/main/java/code/name/monkey/retromusic/util/CustomArtistImageUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/CustomArtistImageUtil.java index 34406ea7..462630f5 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/CustomArtistImageUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/CustomArtistImageUtil.java @@ -22,7 +22,7 @@ import java.io.OutputStream; import java.util.Locale; import androidx.annotation.NonNull; -import code.name.monkey.retromusic.RetroApplication; +import code.name.monkey.retromusic.App; import code.name.monkey.retromusic.model.Artist; @@ -55,12 +55,12 @@ public class CustomArtistImageUtil { } public static File getFile(Artist artist) { - File dir = new File(RetroApplication.Companion.getInstance().getFilesDir(), FOLDER_NAME); + File dir = new File(App.Companion.getInstance().getFilesDir(), FOLDER_NAME); return new File(dir, getFileName(artist)); } public void setCustomArtistImage(final Artist artist, Uri uri) { - Glide.with(RetroApplication.Companion.getInstance()) + Glide.with(App.Companion.getInstance()) .load(uri) .asBitmap() .diskCacheStrategy(DiskCacheStrategy.NONE) @@ -70,7 +70,7 @@ public class CustomArtistImageUtil { public void onLoadFailed(Exception e, Drawable errorDrawable) { super.onLoadFailed(e, errorDrawable); e.printStackTrace(); - Toast.makeText(RetroApplication.Companion.getInstance(), e.toString(), Toast.LENGTH_LONG).show(); + Toast.makeText(App.Companion.getInstance(), e.toString(), Toast.LENGTH_LONG).show(); } @SuppressLint("StaticFieldLeak") @@ -80,7 +80,7 @@ public class CustomArtistImageUtil { @SuppressLint("ApplySharedPref") @Override protected Void doInBackground(Void... params) { - File dir = new File(RetroApplication.Companion.getInstance().getFilesDir(), FOLDER_NAME); + File dir = new File(App.Companion.getInstance().getFilesDir(), FOLDER_NAME); if (!dir.exists()) { if (!dir.mkdirs()) { // create the folder return null; @@ -94,13 +94,13 @@ public class CustomArtistImageUtil { succesful = ImageUtil.resizeBitmap(resource, 2048).compress(Bitmap.CompressFormat.JPEG, 100, os); os.close(); } catch (IOException e) { - Toast.makeText(RetroApplication.Companion.getInstance(), e.toString(), Toast.LENGTH_LONG).show(); + Toast.makeText(App.Companion.getInstance(), e.toString(), Toast.LENGTH_LONG).show(); } if (succesful) { mPreferences.edit().putBoolean(getFileName(artist), true).commit(); - ArtistSignatureUtil.getInstance(RetroApplication.Companion.getInstance()).updateArtistSignature(artist.getName()); - RetroApplication.Companion.getInstance().getContentResolver().notifyChange(Uri.parse("content://media"), null); // trigger media store changed to force artist image reload + ArtistSignatureUtil.getInstance(App.Companion.getInstance()).updateArtistSignature(artist.getName()); + App.Companion.getInstance().getContentResolver().notifyChange(Uri.parse("content://media"), null); // trigger media store changed to force artist image reload } return null; } @@ -116,8 +116,8 @@ public class CustomArtistImageUtil { @Override protected Void doInBackground(Void... params) { mPreferences.edit().putBoolean(getFileName(artist), false).commit(); - ArtistSignatureUtil.getInstance(RetroApplication.Companion.getInstance()).updateArtistSignature(artist.getName()); - RetroApplication.Companion.getInstance().getContentResolver().notifyChange(Uri.parse("content://media"), null); // trigger media store changed to force artist image reload + ArtistSignatureUtil.getInstance(App.Companion.getInstance()).updateArtistSignature(artist.getName()); + App.Companion.getInstance().getContentResolver().notifyChange(Uri.parse("content://media"), null); // trigger media store changed to force artist image reload File file = getFile(artist); if (!file.exists()) { 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 44ad3fcb..7389773f 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 @@ -34,7 +34,6 @@ import code.name.monkey.retromusic.ui.activities.UserInfoActivity; import code.name.monkey.retromusic.ui.activities.WhatsNewActivity; import static code.name.monkey.retromusic.Constants.RATE_ON_GOOGLE_PLAY; -import static code.name.monkey.retromusic.ui.activities.GenreDetailsActivity.EXTRA_GENRE_ID; import static code.name.monkey.retromusic.util.RetroUtil.openUrl; @@ -60,7 +59,7 @@ public class NavigationUtil { public static void goToPlaylistNew(@NonNull Activity activity, Playlist playlist) { Intent intent = new Intent(activity, PlaylistDetailActivity.class); - intent.putExtra(PlaylistDetailActivity.EXTRA_PLAYLIST, playlist); + intent.putExtra(PlaylistDetailActivity.Companion.getEXTRA_PLAYLIST(), playlist); ActivityCompat.startActivity(activity, intent, null); } @@ -102,7 +101,7 @@ public class NavigationUtil { public static void goToGenre(@NonNull Activity activity, @NonNull Genre genre) { Intent intent = new Intent(activity, GenreDetailsActivity.class); - intent.putExtra(EXTRA_GENRE_ID, genre); + intent.putExtra(GenreDetailsActivity.EXTRA_GENRE_ID, genre); ActivityCompat.startActivity(activity, intent, null); } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/PreferenceUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/PreferenceUtil.java index 9b93568b..4b8e93d7 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/PreferenceUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/PreferenceUtil.java @@ -21,8 +21,8 @@ import androidx.annotation.LayoutRes; import androidx.annotation.NonNull; import androidx.annotation.StyleRes; import androidx.viewpager.widget.ViewPager; +import code.name.monkey.retromusic.App; import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.RetroApplication; import code.name.monkey.retromusic.helper.SortOrder; import code.name.monkey.retromusic.model.CategoryInfo; import code.name.monkey.retromusic.transform.CascadingPageTransformer; @@ -122,7 +122,7 @@ public final class PreferenceUtil { public static PreferenceUtil getInstance() { if (sInstance == null) { - sInstance = new PreferenceUtil(RetroApplication.Companion.getContext()); + sInstance = new PreferenceUtil(App.Companion.getContext()); } return sInstance; } @@ -279,7 +279,7 @@ public final class PreferenceUtil { public final int getLastPage() { - return mPreferences.getInt(LAST_PAGE, R.id.action_home); + return mPreferences.getInt(LAST_PAGE, R.id.action_song); } public void setLastPage(final int value) { 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 01bfe30b..a37f0233 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 @@ -46,7 +46,7 @@ import androidx.core.content.ContextCompat; import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; import code.name.monkey.appthemehelper.ThemeStore; import code.name.monkey.appthemehelper.util.TintHelper; -import code.name.monkey.retromusic.RetroApplication; +import code.name.monkey.retromusic.App; public class RetroUtil { @@ -84,11 +84,11 @@ public class RetroUtil { } public static boolean isTablet() { - return RetroApplication.Companion.getContext().getResources().getConfiguration().smallestScreenWidthDp >= 600; + return App.Companion.getContext().getResources().getConfiguration().smallestScreenWidthDp >= 600; } public static boolean isLandscape() { - return RetroApplication.Companion.getContext().getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; + return App.Companion.getContext().getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) @@ -199,8 +199,8 @@ public class RetroUtil { public static Drawable getTintedDrawable(@DrawableRes int id) { return TintHelper - .createTintedDrawable(ContextCompat.getDrawable(RetroApplication.Companion.getInstance(), id), - ThemeStore.accentColor(RetroApplication.Companion.getInstance())); + .createTintedDrawable(ContextCompat.getDrawable(App.Companion.getInstance(), id), + ThemeStore.accentColor(App.Companion.getInstance())); } public static Bitmap createBitmap(Drawable drawable, float sizeMultiplier) { @@ -297,9 +297,9 @@ public class RetroUtil { public static int getStatusBarHeight() { int result = 0; - int resourceId = RetroApplication.Companion.getContext().getResources().getIdentifier("status_bar_height", "dimen", "android"); + int resourceId = App.Companion.getContext().getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { - result = RetroApplication.Companion.getContext().getResources().getDimensionPixelSize(resourceId); + result = App.Companion.getContext().getResources().getDimensionPixelSize(resourceId); } return result; } @@ -337,9 +337,9 @@ public class RetroUtil { public static int getNavigationBarHeight(Activity activity) { /* int result = 0; - int resourceId = RetroApplication.getContext().getResources().getIdentifier("navigation_bar_height", "dimen", "android"); + int resourceId = App.getContext().getResources().getIdentifier("navigation_bar_height", "dimen", "android"); if (resourceId > 0) { - result = RetroApplication.getContext().getResources().getDimensionPixelSize(resourceId); + result = App.getContext().getResources().getDimensionPixelSize(resourceId); } return result;*/ DisplayMetrics metrics = new DisplayMetrics(); @@ -415,7 +415,7 @@ public class RetroUtil { } public static boolean checkNavigationBarHeight() { - Resources resources = RetroApplication.Companion.getContext().getResources(); + Resources resources = App.Companion.getContext().getResources(); int orientation = resources.getConfiguration().orientation; if (!hasNavBar(resources)) { return false; diff --git a/app/src/main/java/code/name/monkey/retromusic/util/SystemUtils.java b/app/src/main/java/code/name/monkey/retromusic/util/SystemUtils.java index 2d4b7199..5e4b6072 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/SystemUtils.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/SystemUtils.java @@ -7,7 +7,7 @@ import android.content.res.Resources; import android.util.DisplayMetrics; import android.view.ViewGroup; -import code.name.monkey.retromusic.RetroApplication; +import code.name.monkey.retromusic.App; public class SystemUtils { @@ -38,9 +38,9 @@ public class SystemUtils { public static int getNavigationBarHeight() { int result = 0; - int resourceId = RetroApplication.Companion.getContext().getResources().getIdentifier("navigation_bar_height", "dimen", "android"); + int resourceId = App.Companion.getContext().getResources().getIdentifier("navigation_bar_height", "dimen", "android"); if (resourceId > 0) { - result = RetroApplication.Companion.getContext().getResources().getDimensionPixelSize(resourceId); + result = App.Companion.getContext().getResources().getDimensionPixelSize(resourceId); } return result; } diff --git a/app/src/main/java/code/name/monkey/retromusic/views/MaterialButtonTextColor.java b/app/src/main/java/code/name/monkey/retromusic/views/MaterialButtonTextColor.java new file mode 100644 index 00000000..b5ff0287 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/views/MaterialButtonTextColor.java @@ -0,0 +1,25 @@ +package code.name.monkey.retromusic.views; + +import android.content.Context; +import android.util.AttributeSet; + +import com.google.android.material.button.MaterialButton; + +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; + +public class MaterialButtonTextColor extends MaterialButton { + public MaterialButtonTextColor(Context context) { + this(context, null); + } + + public MaterialButtonTextColor(Context context, AttributeSet attrs) { + this(context, attrs, -1); + } + + public MaterialButtonTextColor(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + setTextColor(MaterialValueHelper.getPrimaryTextColor(getContext(), ColorUtil.isColorLight(ThemeStore.primaryColor(getContext())))); + } +} diff --git a/app/src/main/res/drawable/navigation_item.xml b/app/src/main/res/drawable/navigation_item.xml new file mode 100644 index 00000000..4238c61c --- /dev/null +++ b/app/src/main/res/drawable/navigation_item.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/navigation_view_item.xml b/app/src/main/res/drawable/navigation_view_item.xml new file mode 100644 index 00000000..9e3c2556 --- /dev/null +++ b/app/src/main/res/drawable/navigation_view_item.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/font/circular_std_black.otf b/app/src/main/res/font/circular_std_black.otf deleted file mode 100755 index c62b210c51fc6b0a7e4022ac9dd54d50cf5942a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74500 zcmc$_2UrwIv@lvdGu;h6I11wcGWN`XqU59kiV<_p0V4wpNs=%O>Y}2qIj(WdV#JI& z=bT;Bx@%n5U9+yP?rAJs-{}VT?!EuL|M$Q5zV|)G>dL21ojO%@&Z%S9UcI^!7g9vj zqAZyy_fv#q&TrMTeOp3^ijZ#yV(iqmTi2e?Yi{@k^GSrvEo$4dckG4Z z*X|I;;|U>JOV^&E5#2RC7ZSpRVf*;ROlwZf>i2FFB4E8{aH`FkL`*>%thP;V(t(eM*b3ws*(Cz!eTsC)erL>D`I9a>Ba;gv9y9l z#o(W+h>*~3KZ7Qa5s@_)u} zELUQXzb>S4Wm<*fx2sI6u~+BHw1&8m%*r%NYLGFNX^yy(?<>#xH64=r;e#iYlx5fo60mxBGk(&)7-zxc@nF>T3PNyTr{qgX=h^6h?QwA z3DA6n=|b9QY?b9zRFgHED$_bLn6Fovu0}lhuXwr=f!|PBZul3@?lR78ZCSQltKF7l zNz1ZCgoW3$B#f}M$sDY8ol9|6OiT%KO-I^U|`jEa4$xVc{{Bg#5IO zBuiLWSj5++2)Y8o*8)pgp2cdh=US6&nbzE)mh5Cp#jurMYksaRYosMMH$Mw!mTI@> zGztwZC@2WYP>>5r%+CC83qyOQ+5%hI^3qbUTV5w?L2`bEB|pz54`5HVS(1?^d6w*4 zC6i>eTP=xLVnv`ymVz{UDw6MOGfQ$>hAq!x8=jbvpM;6b?4-2h5lFEhORmkDmuJgN zKxC909so)z&z_x&2@8@UCm(AQGe%gf4r^M5H6g=h$+P7;5GlEPQd*upEg|2ow6blkEl8TpOaCkzq-&S#ol-^K6Ov z$N(X@?70?eQc{{7SHzm3taWatRnFlkwkpDZPqzxX%XAHu`6jSaTB0p04^jHR*}R7> zMP??_me@!ZhFoGLc8rtoI7q`13yC0MB%IX4R00`+-!>#0%k88SwoJf$PfX`y`!p;M z!F)@MbFgJDmZXqWtgV2QjO7-rk;jo+XJKs!wob#Ai3lMB;pJAslShwLN)pLX(gXXX z{L`Y7($|J?vq>Xt_dh`h{uczf-Pd{i*Y-00N#x(H`zUnC!*S%9T5$FuNCTM)F_=q0 zOwtfH8ArJ^3?ctlnDUJCu-=NnuFy0IVP+~b9ExSxBpJW|p2>esTXBqBg;FE2T`Z>a z5tEAbktu3NIyNGq_$yF;A&8Gm(F&@BAU(5jwEy##p`;hKwvj;63cvEqQz}QxQ`Wfv z@s(G|g5NxB@pT4vrI!V3D|kYlbGEXIUu%+Z&US?kGEOqzSSvA==~sYhxn%{XeeLrV znhaN_w2YAjzr%4}8Hj;QOSvQyAd2YF}9JJ5;nW6uQ_rLOM=s#ympi-ZRaI+B43hMoT3;loH!4>Dif1E;!^rXhG9BNib zEUI4e4X@-is%VU-qGZM)DF_l|H2K1WGqFrh#>xz5Ml*9%XI1A__to>%8`Ou?$1F}3 zt);rf%VM(lSn629ER8I&mR6STmK@6n%M{CIZ@ssVcOCCE?`-e6-iy39c<=H)H7 ze5(4m`51h>d`v!pKJ9$seQZ9bYoGQl_TA}w(f4Ydkh=Q1hPsmPChX{l+kD7ZBI2N%1g=pS6-y)(RLdoMs-O8>#d z6>+KY4=zaxE-QU^_+I!2m!e06qO^**Fke1?`Sr_*FW-F`_a*O3%9rk6I)7>OH~%;H zS^6CNIplNA&ov(1c=W@g3y;n|I`ioCqZ5zzKid9ix`)~aUmpDW;L?Nh57O@qz5CtWv3G~w zO}#7pT<2%spALGr;@YadO1TPY{(t{)Dw$hkCX)H+|IMG8Xi%Q#P#wxLF7YG&BmggiAiNes z@RE_Qz6cZn>XQZ}l0=bc(h#qo7}O)0kfx*=X^zKNEM9`GNNe1}ZBeIaPdbo}xQRQH zE~G1}Jl*jU?}?X8Z_vIcnyeyg z$U3r?tVca#6WK^MlPzQ$*-A>ucCwS~CcDTUvXAT~hsZ&4m>eO$;n5^t4Qb>lUKPc7 z%nfB!csQpsL1Z#n%=nW$CYTAq1G9kfW@!9(_j{vO}-;D$$YW^XE={6CCkVXa)MkYH_0&6lYSuA$w+dG+#pv_=j(|y zl>bmOl>-w;dN3=AS@n$g;z{GLP9=3!KO&`nA!ep8@x?%xtE9TBF9~5Qc2;4~U>jBc0V6Oy4G*Rc$duW4=1(-;vHtLnU8bbspnLEI*8a!QhYew#wL!byggA z1ktOGk+!NMM9@?vTGd|67bE;{No}Sh!g_~u>4Z9C6CU z5Q_K?#lAy`MWz)al1Rq%RD?dM|Mb>hX0#(up>Q^ZqH z_r$axhFHQe&vE>*NH-6}VJwc{8Fz?G`xp|d^2hPJk!UP4s|tugZlek$jhI!$9re!I z%sLXGs)KXui~W)IN?8Zu&NzshY7o+NItgQzkWeOqxT^+}R;sO7=ZotUh_LDr3-g#n zVgG9CND`p#j$s0}{S)z$>HgmYO?|{&UJH30{uP*pU#`e&f$?1o&s0FFeq9H7E&d%; ztt#@$TFC41HOOm`fN&F$ZZh5fo1jj{a(OLq9sV5{@ykVA3tW#cw=q0ab;SCw>maYi zze8^lqU7bZ_;D_ZJrQ|I{nKdHS(XT9LI6~@=hIx zd&iSB*KEOdC;-azQf)zAKy^pe9nY3)I7j4L)df5kx)6UR7x&2!ToXJm@NB7%AzH4N z`5$@MssyBqx(&jajPt3DW6B{2>sR9b{!`(3f8_iBZBU*4@>*lVe#eNvY6tF%@ff}( zTJ>cNR}~u}ztDEh|MNA#n~uo)n)!rwxXUDp`HbNt=6hqff$3LRZo$xXn%RwGH1cMB-ItCd|M+_a9R(OxJLYl~Ti+oPw8Lga`cvdn^@l3<>Pp*^CF_jaZ zbA_a_CJbrwb&ohBzIlk}{}7mMxYw_f`pkPgPq*N{{RzV@495{~T=TDM{dMh`c%o$r zaE}M$_*Y3ari+r+GAl_{C1j(tBcB1`81gVoLMcdwiSQK39ryefn{4a-U;h|1#u7BS zx_z~J{bL4`OCzLeq)ewO)2SuBhW2$)-*wS+r7K0W_6B^K!D8$gU-cA5gA3j8TjQ21 z`GDY1Age;Eqp;pFnVW$*@njn_E{!ADX#WZDFGhLY8%n&A($z~iV*ni7RW9Bf6 zn3c?WW-GIsImVo3E-|;5d(5xQ3+6rZNyVzFsN7XPDt}d&s-dcxs*S3%s+X$2DqfYM z%2MU43RUCL6#bp*d({fnI@K1{F4aNR2~-xYseV*FQ2nNQqxwtrx7tbVs`gU*sDspz z>SpS8>Tc?O>IC&rwOw7Po~WL!UZh@)YR)$GZuLR+arIgCW%Uj9PwI#2-_$SF@6@3F ztWj&6HB~hRjZx#RsjCUr)YCN7G}E-z^wJE{q-e4<4$T;7^y{3%SuvJ+DYi5JkXtpKWiS5k}Vbjao2Wi5i3}QIrfWA}mS-^koxpjDW zy{?(I6swYsRG@}ODp12C6{z8n3e@mO1!{Pt0yR8Rff^pEKn;&npoT{(xP?cytN^9ssEZ>@um2FpIv_t19i4?Rv+ikf@Iw{+ZP*Y`? z^;(bAt`YZo5fpq_$wWQ0Pf zsBk40Re@Mk#X3h-taDVwI!9Hkb5yi4)2NDdj;dJasET!tQr5XOva3SUSb3J1lL^%( zRw=|t9ta~DN}JYg+Kja2W+R_x=4NNvl-OQSk+-Mj+A2ztv-5M6s^m0BMM+-TaHSy6 z=CEZcDVzMLp`^0Vkgp^Z$Vpk*nKEQKmLV�$ENekd=fCSx(51l~{o+rz#-JDFw1p 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%0D3`R&67{=)GDS8?p!cah zuITs-ba>1f9aMil4Gx6M$_d04eF&xtQ_pBd$VjL(hW?*F$YTaMZ1NW#b2xEE(-zh^ zEhD2BTJik}-ze0Z2KA;vy=hSIIjA=c>P=%bO$$B3 zdJ1Q7``PwVFz#QQZKYTHNFh9k$kdg=Wi>E!l={2Cjvr3s05jaDj9cCyH*y6`6$ewy zC5+dVxvyqk1-B-noXI?LlsP00{b)4U)8NuH=90U>T%^LB^z|jAS_ZwO@V^@Ff48{` zEJlFE2skheE+hgDOoIc{==T9Imk#Exfw`+-?h2S=&l>Nq^X>*MVVVJGNT}Hxq@fTs zd!c5pW>K@3{^Zqqa`ocs&DDpiFIPXV{#*mN26Dj@(+WkQd6Ax$K`WM)?mP7+k~gB; zjD(+A0EBf^uaudiIrwTR+7s&NP`q034Rcu>^dhqJ4bock*q-1#_N(Nnog~m zdB4`&%&oOBOK2@`{HnDwJ=$|Ou4}E$s#+WKF0HM37ucx)U)tPUq3#xNw_V-M%eWht z5jrj7o!o6urB9V|gR9=NHtf0z9o1zJlIn&6=XM73sZzs+;JSqo4;( z(TvP_YUTq2a(_d96$WD9d=&3Sc!H!?eJ?$zU+@5fr4Qc59C(gAi!%qOF$W?6k!}U^ z{yNefrtCSySWfzb#Q2mLpHj{kpv%J>5AlVoOJwBVNbeABLr&mM;4BM|8$r&sfYt@H zqE8Zt@&bDz5P6YSMW2DrhqqEAxwB?`xsx2PlSW4Jdi04UwCXZ=^%O?<9JcecS3PK@ zK37Aor@5ZtYD^AIpiw(9%4zk$M+2^BxEiYx&YR`6M&Klp@Vm77X!qp3JSA!k(0?{0 zZ3Iub!rnK8dL$N7ACR8E@hc@?AgxADXDHdAWH>V9kY|Y{CFfMIH3Sw5CeiVqzi}xD z@wH${nNT+YA}2IxLW3ssXF_|X_8eDh@@NB0QCd^VV+Plr<7!PgnkIcdLTg5tJV!vg z25F68c!bshxFtk#zCb)lafX}?a=t=HBr74BPLnjiJ1CfXFmW=Wx}gmtRJ8Ne$;kvV6Ua=)r3fH1$;|{R z6HS2zR1r5p6%&LZGi7)xRTolcmS@@=jA4>jsD=^LP@{&Tp$Uf|TRvzqv)9&wF|)=N zjG=r5bCR!S%Xt%wx2|0X-&U&FEYRF4#my~c)gx41SUUqrc-ssNVlIHRkPNO<+|wW}k#LSHMD*ut2eu$W*G%@ z*lG|mi)%L5$6Rx`_$Vgd>eTqkr}TJ%A(0ul0#C96caTv@_->JNWIRR!V6588+$LkJ z@Bz6Pdt!+#+{azPwYcvX0}rY(P{y@<$Qg;Gv}*7hxveBO(I}=dc6_IF02Y^>hg|nF z>Wa?ELH;@Tx433>mhexpJhQ$Awqk%ZH{&pRYM@Zk5+xO&2C~A0wj5sIINZaxLCr)- zOP(ZsZ%XY?*bwIH(TvN-c_;I{$dRYf9@tp3j4(H9ciR%Q;&9s~cqjU7@C4@_0q32r z+D!Dv*A(t$?veQ^qU3z;Y^6TFe=M9uX>1REr|lTwO`R5tCQLKsxME(WZt&^m4Y}i& zgEi#;(E)$*Hx6@~`8?PwMw`ak;?4hV;(9Uw3n|fy)<_@zv@`{ZZ=ZSMka<;k5v*6&ed7P(SX`}wlOE%e8_sYjVss> zZjO-s4RpMKI&R^AZPwbWe6mKDT!l8XjZK;9q&-51xzg-|_8oebaPvBHn+^F@9p)LU z9x5kUrRT_=s}=KBzx0Rqp<3yKUr1k7c}cx(^vpEo7P%7%EP_&@ZS{2f780udi^qRK z&v7!_nd{6{^CQ|v%ji>-Y#t4@D$pOJ7lIx7`2pst+a1)F+``6E83$+^g@xNBk{Gu; zGHEG{av06hDMx0bbjH(v@gPTND0KZh8E2uNJbwWj@qeM*FnluGEeGuRw9ZFRUA8{( z7xKy$<*(=PUr=4PGVk>G*WtNSE}^i04l{UO%6RKlqe2M(LHf)bX(%TNu2hcbTTs0< z4oavsi>rL-F|yXk%7aQJp`mceCG)sb&Y3ZT{DCC1dZPI&W74)PFlOed?tnlv2P%($ z7vGXSyygGs_;+Fb=d^Ki^<>UhSXDvv&x~wteiHaMRcV?N)gcjec^2#!2 z$eUuJJgxsMA9LnYFnoQ6{xh`z&SL z92E}p7BBDo>ThtGcg{#c|1~_{RMh`2=PX72e~*p-0?#3dN&X9D!D`kAqz*V?kq5)L zm8?;ClJ&)Za{RMYR!Nqek>4sAIYUFoB+{iFeMp{cz+~m{;#5qDBiAi^f7bN>1)j5J zbmylG$w{Q9KRbfs(;tjwNA5Tqgl3A+|DR?2r{VvXu<_r}#vSW$r}6ua3I9CsU&FB! z^{?VNOYb$Ykj$t|Y{p$Y{Wz#gvEGHn5_mGxWF+D3S*~NypmoG#a=&4`DY{%@i6$l3 zs>o5ka192bf1-QKj*WjupOA&S3aiz>bDJIJznyF3-%jzT3E=AwVsnjdR<(&KGGB36(Q>j-y@5grIGKLIyivl_AX&a@rGu5gTh(g(ux|7-VQeef?NprwV&-G&sq zPBhoII~33jKTU{yCoiPC3}J{EZ{nZfNLkZo$8i%|D3x&#{pSY3ALX0T3<9 zonzcCRA>%LVV;%4#Qxs8Hh~jQdWr6ld4lD4sH=6}Ryo^uAce}3b~^5Qr> z>ua=+(hG%S400-rOTpg6TIzmm2+q;J_QOAj%|-Z;!7&PTVEJJ2nPKBsc;wK1c9{P< zHbU@)a?1YyU*!BJqxk=mIcMjvpmv2w#y6Y1tHlhON84z zknRq>hIpt4UaQ)waIjn_&dA|Al9YD638-g&0=@M=gKRY%;age7j za4-86Ij{x1B66rYGBif=B;in$=omsrJofySU76@|vv3}vC|Uorj~iJ-|C^_OD`z-) zp{Ssxqj^eUjrO3AWW|QO)0NF+2y9KPtCW=v|9_>I{#!7I0Lc9R-{qWH#$T&rc>Fux z|6`c(k%GNy6xN(hY@DNMft=WT#v!%H9*w+M6z5kq*h#j6SQFpP{;ML`Uq8qWiwf)x zt<28BYV53O$=uBqQPmiwbpB>2VBJtETj1oq(i*N=$npR^Oy_?-cCu}G2 z=<%EFcXkwAWJl2@b`xE(v47CU9wPQOr`xXEZrDtSCPFKMzwY8%39Y17N-M41r!~ei zQwOc1)(PKCU9_%RH?6zY0}oBT@Xypo>#OzC`s1T%ARfMxwZYo++7NB1HVl7FQ}EL? zS6hUKre)d&ZKJk{z9{>iWIq{h#i$!!O~wP2lhY3_aYlue@L|D8r zGZ*Rb?E_{X1fp`B>_p<^8ws3F%BhS+c~#CBFka2p9;lmxWAT(VnDVV%eQn`#+i~Wk zB|4C6N6vh-NGDqhTBVC^C@s?!c)M{nCiOsCZ4l>kwAAxh#1G-D4OWIxj`-KAM$3)1 z)u;6)0m}?dJFPfVd^T_vpf$I$|8O^_>{?`}EG>J2x}M}rpoLGf|LQl+4z%>|z;c1J z6D@ub&qIH3cA@1j0nug7ZcxA#TX(2H$BS2hvnSMW)z%A&xQ6#HK1OEi17)P!`a&Jo zZT+B-8~BYeIR~iWh5g@Jgsn9cQ^Zytswv7o|KeJ4TO+8agslt|RFZwnrLA1dq2ZsIQN2`K`#b!9hbH}*W< z2i0}A-4EsUu$5K*!5)ADd)Xd@3VYigffD=J9)%kF+8&1@`?0^IKfBoKLYV_>4WZ6~ zwr8NwLAI7qX|k;ylsecp2x@)amJG!Xu{{sf4z&$|a%m0aZLE^=Hdb7D8+#H;o@=WK zH7~+nh4>sRtbC3&fU-A;?;iGeRnay<<4;3VJ!v&N{Z?$Ci$H0)prqWKN&L$h&zV=z zYhE-oe$K+28gZqs7ge-eOws4P?1{}!>zCrGG-o7xOUrWq0B3$hyCoFu=4U_cL%e^O zv!v}2&M3SAmWMJP<+Rhw9)q$gaXM{PI14~~HRy3qa2Cb`NiAR$4{>hjrw;c|aTaEu zS6$U#q{r09_gi5+zcr#iKFyiS_6%n%RNa{SCY-UhXF22PgH3_Dnd+}C=m#G9X)CDn zInLtHe_KlL!C4AQ>qYPC!QoC&r~oP}+lb0*j}bKb4|Ddx9*!I?{WRV>M_-5u0%C#M7dio3w)mz>3I z|FQi-Eq>)JW;?_gXFJSUl%2gtcyp99&UTEmDE{zHK?7$vV{B(R3uv4daH%jd<~CCqm0&a>a0 zLdiW1SzlIyD2Wz~f=fAvW$!UtJ+#{A36=94dV(;{@wPdJ;I)}2R2<1u4v+d3i+s(x z`{%zwcrOOw&Ey#Ie-)l9c2}pvpKo@vvRanIhr|-_6U<#vcrs3dsbOVs6*nDp1Z%&LfNy-_8iKE|8Ip~m9;K)h;}DT z(h6ehPfBEk3`yIZYPYqt-5F%kAcjJC zRHa`*&X*c|fF_w;RJSXJeKpIGQP5>rap(tU)lQE;g1XVjzo(73xg4a#(~K>$>p>{* z5BxFR?htqL}w&^0;E@ z3P=Sv>#Txz=S{hS*U*6{lRF;J zFJ5WLi`73_u_ni}N{{vv)T?%GL0w@Z&>b_oWuRJn6litxZF1ah4!t>=s)t48bIjkA zMkUOeIXph&!-`;k2wG;6R#usyV-+tn9)~CTTlZ)Ltkq$io+kVJUb$0p^F6hr z@C`c)&QZF;vVtJvE=wum6HdwB=*+Dck?<5Y_`+!T4Cx7By&+4=zxcqKbjU_b=1g`8 zg5{&!6;+VRogDGOy;ET$vK5f^m-TIHUhpv+DtMJvMe2NmJJBhy>o04XzxfmcK6kQC zeja+*#r!pj5=E1UXG7lUj1t?~dnIcOXPNzFkEM)jz>Fr?z$hjEx#(^2yeq(&OFDwoJzWgRnkR_k}h0Ix^O7zB8QSLB9wFyp`?olC0E#inqBwGi?*iZ3a64QqLo}> zS8|0@$ra-F>n$Wt@fjw18}V;!N9q&pO>z2C4x~NN+K3Na7w`E{84{J~ZDLf-8>yOC zQZ;V`6~`gxN&RaoeU6JIS5kEYAgV`tk*ez}sk)((s;elex}lP)t0<|up^~brDXF@;lB#PcO;j!<>lTzG z`l#GW!hTXo*iR`5yROno`4lBpQaYvlN~(T9N!7KLR9#L<)%BEAT}w&T^^{ayPD#}d zDXF@=lByq7QguD0iE=90x}uV;t0>vJfs(DODA~H9lC7&KZB>$zu&XKwyM~gmpHvcd zeI;Q(sU++Mcy!nTWr$uYUP;^Kl(bz%N!t%AX}g+|wjWi}b{!>cKczHacGW8?sb2A{ z>J?2@uV|ur#j~ncG*!K#iRu-NlzyzF(u0*zIYDX>4@^GzVwWe%gZUb{4ph$ zH&j|9uhJSZUzC^uwcGBEDtQFjeS;Bm6Qle|B*`o6#XiS~yvO{J704U1GZv16z4-i! zzXKVc;V{@GgbadTsx$om!F8Hcc$DD!fw^fTbKhxXFY)yt`|*#O-{Ec2!^rq9D+c(6 zBi>Z=C37x(ou4(hspd3wPUUWo?9(S-@htW?4sz9iQTU*Zl<^}xSS+)I?4K6M4$OLMei2#gm7q!0HD#70-)c`|Hftw>hjB5*WJU9|(g_R_WFX zukgXQJwmA^Lij!PR`tDALM1Dn7fpv8p;W5=!V_oF)CjfU3nxe&V9t^&GFlJ1`jtEc zQs}~pm#xPMy-0j6E46S@QgYZ4d=8Z(Axaik??e5Fcd9Q3rM6)2;%yMR%={QFlq3)I zGOCHCtVmQ+Gdp}l)?O^!qHxi6p#)lZe~@EUIS?jZMFk!UFN)u0JQX~M48%ccDN3sN z8J;DRBbN}$ml)hB7~#c5dvg=K!8qc@^p*#8u<)KGG^|f%d7t$`==RnJ)-9uM76@-ws*JDnyQ~m005r8>v*4td`4OW1u|F`0^Fw@&zd7nwl@7nV0sH!&YEfVa{L<+=5-pFHp&4 z^LtCTp+?>>kwteg2U~uHC?&H@EV6B+r}oNw&Kkz0tyq{}SNmKpb1z>mvEfH*tCHv? z?mlquRUxO;?;nIktGgd4^?U01Be{t`A*3YU%RIdm9m7`gN#gDYa}3v3TQTBp0OBn? z$w^zN^GD0-9Qv0Y5`JVpsofkILAlZYTm~y^z{6gAV~MY1ql=6aBAF=~M28tZ zW~wlO5qim-jl{MpIFn`iE1~X4#88n?>3-thkRA)Kr24JoVIANHU56T1*tgX4a8u(;URO8p|?vWL!9>cWny?gdtLWn>8eFwMEVFzIa`MP%wX8Lyhs{;x!Ci$8Yuo{Q!Zix*q0^v8 zL3vg=#6w=pHdfuRFl}K${RZbw)w@Gki_fzRNU~6v72{?e$m%yTtIPNj93K@`3IB|* zZxZ5nEL1?~pWcw61yNKXd&e&YsY-cVqaQKjf*F&zYQ$8bv5Xpw$GoN2^AA6qk*6VT zpsvh!XMrN)iI{4(l@!5#^c!+=#*?}W#wOKYhi<$Ui9>+#>P|WhKzFkx>|%B#w{yEA&XVum$WcW7 z3WdoLK`-9MzZ?7I2&Es3z=x9Td-iZK=Mi5#Bo;tYD}X+7x6(I=&Y>{tS`mzSW09KV z6get%{zu`Duf&j3u)Tm&are9vAB=cmG#4?p;zu41buCg;I@&xh z{fo2~$1Crp9{a5VgOHqHWS7`KW#lAt6LSkAoAkeP%t!)l9xy30x3!9aZdIsAs3XXQ z;^$fN4dzM;>n_`f7(p)37@?{1D+hOyQrbE$`zj>)D_Y?@EstfU|@uHh7^dpi5+ z=5tznnOH#u58iBJ_Fz6y`AU84q}@*r((leNQ;Swy_^K=N9xft+9WUen<^P9wr|GpL zWv+&oP*Rb~*uu+l@<@Z`bfw9Ur)8wqd<0F&SSNe@&^qwHo$H2pu2F9l-AJuf>>COa zHFl`r2=ZGe>CLHJSQzj-PH`oIn>ghMPUWun2OGJHUQn#yZ0s+J04K@N|43GIMsoIo z%17fF@+W~b*qRpIrGc}r(Px~>z>5a{7Lmgq^Ap0>fQ_N%EAUD?f#^f>*06kq17dVQ z(@o0x<}*AQtm3SME<<})WF$_LzN%=+g4DXV`U~@PkRDWZi5H?@3$Ba2wtBkILD)fS zF}p!U5*i!}sJC?=mt7{Oy+!9KI!oso?cUV`(uqHgIMr;-0+!OUSR zDz#QnXFM6VFAEAd792xGA8|u*rh+HZaH=swwY$t*AqtX^%hr>k1c;LO=hgUa;mi7! zFBfpoq6(X&Flk2)zO5h>BnSKk$3+rsiJ{m$UlgLr ze9pYf%#&Cd`DcdTd`Cu#FC%_9#mI;um^cod{46WX^p1<6J?T7!VbaPIcrCoBx$_oy z362g{%3DKEzSqe|=+tT%OaCF33NmATEhPbk;6>qGatW5IV9#1(tM>(=6X`p65R2OV zl_OlT$r_)YdWC+(TuraOVrfQI-wG>7r6|U4G;+Vm+EOg*IP%QFv)n(*ii}OY zN_jH!pl@ORWoI|@&dq{sP8q(moIh-eMPBX@5HjA5=m!W=SMNo6KbsPL8J%`YNYVRPuS#QH5!gprSZK%bA)T20= zVEW*FC}l8rMg%Kv=>0$cr>~e+`4&7OSTb{@teeJ{n@i7{PDbdP<4B%a-)4QvEHmp} zFji=uzA2ZivHv$u*&)sTe7jPExD|SkULT}~VC*=hJq=4r8weM2iz92wtWURy{@=a| z!*%wovd}Vq&9jH|d{gS1V_AAr#aqx1=+9QavBn|MGlXV=(3n>?vmB-8fQt@06xNst zWv`JO-i$EC`33dh!BH#}7Mj08&-^R&!9r%Qmvjm*p&H5y#p$RU>nSUC!+1*zqgmP_GnvXK9OvN0;_0&HmnFsj<{@i+ zgEhWIQCVve4!afEl3qx!G)a8ZFYE}-?~+Gw6jRg`7A1I#Ja5?ug`TbX*P8p)JZ%La zyJR_nxn9ij@-IDzhW+*tg^RgWsMH^M@<&x)BUHbeW72#JNn!>~RZl3;JHlU063{}*^NsFsq&Ys7xZ6;AY|q*h#>@L5^fJSz`$ z3%)K`u$8xksqm75!CC$+orJtnGa~$+b;qyOyE62@62e<%!K_E9CTdMX+E{eGw_Ur+ znwHqs*p~4+lp7&J2S*jRX@HlEc#1YM;>(R1^9plQT)3Q!8d)Q@@M|)1da)S_X+26y zs^B59GDktVQ=>Aqw?-!UDv2fQ#)MOSMk%2-3X{fz_!oPKBXD!91HjiE#GPVF9{Y~D(g^ojD#!8yx_Ymf5HB&pn*P>78uROULq;#=UXQnL3%+x$#Ph+)hlam z0v8A2a=&A3Y%jXTgWSpYzU0Ua6U78Ld)e>Db5E6Z2-WCb<+@T)K3syD%&kwp$r?=ssZmWYa8{!d}o8WiPq zhR?gp!U~HbZiE0T8YD;^MWQA#N(>SeZ+HP?KoOMSB?!u*fcgQ`?!g(~Lj*Bc17VI-TicCN{N~={?iuc@E1i$fWzto;~0BuIHTha=ve$ z_c{7Uf%nGQemTO#$DeulEW_{QS*}l#ys)28m-jI8eh&-hocS6o%?-S{@p~n`_l_T) z{EOP(Mo&;es3p;auS5B5jNgALS@M>C7>99SKJz7?DiCa{&8zN|G=B14cmUoc&1U?6*X_yyoAx<#gefGlXA`2*2#_Z=x{ElZ08$5<+>h5XuEWD6d3+ zt`f?6HDZ9HLMz`TwDMG;mGgyG&JkKU7f9s|u(>r@@`PE=2WEK--cm6JI7evZ=|U?P z3ava-XysYLDc>W6a*+_qvy&ow4H)E$sOhtsznSs9VIh%2LL!HSM4lida;A{TlhBT@ zqs8CA5(NgCEK^qzt=$g_k&E)oX0Kp5mAVUTl#K~59; zI3VahA}>v(a;_Fjw|!TaB3YYO8j6w#L>VzZOxuc|!Z92<@9K zwC@bm@Ar_H6vo(Mc?vzq2o!oe&X$~x6571E8tu)4RApj2(KKbk&z})~{t#+C8FA74 zm+S%5n(qb~JAM#R$1{L}FT^rcxcCC0;>*QyFhP=gQrLLPZ!tIlh>Zu_7)y&}%SpC& z3mfkwPc6d6J7MEnBwJ3h)hgNABiY)UgpJ>qgpJ=XIeXGoU}+RWKEo}?(k?EARN>?+ zpbsl>4~#)?lPnGhEl)0lY8R9I^+^5(B!7dFzw<)N4@mO5g_iFTTE1KI*CYAsmi(QS z{5>uC>l0djrKIo~q2(W!EcQzl&*2^$(eC7P*bdEX!hMp@Aq{*FyKskOb;w0K?LnSQ z4m-r;a6ecR_M?PR>>a2<8?=?N>|Hpz9p#K@Kcx5#{>D&3=7*(Xepmu+KZ+9aKYU93 z5A(qiaSSD7fw)&J5OWmuam1Z)eJCd*#C<~VpOogD5_-Qy=zS;j{x0c^lg_k=JEBOM z^Mo{KuQX@Bdj*RVhCg2z{x)Iwd!#vo(wrV?PPa6tM;QL7^yirLr%(FRFZ~&i{`5}11uSZo-}#fI^S*f1&; z1+YSF7*%4!cvNf{HDbfqC^n2uV#C<1NP#V4!>AP-MxEF&wu%koF|lE6!=np4k)jD6 zvY^dJo-zF$w!ju4&&d7`d(f63&-nfhtFo2IGs-_^>#PR(jkXc26q_(Ft`k2-lhs)> z@?^;fiX|gmEE!p1$(SsbjJw2=akp49^2CxcO)MGH#gZ{YEE$Dj$(SjYjC;hAF)ndu07E8t&v1F{ZUV9C9c*U+DdV>5I z>lFvlsmP*dt_jB`*&xX=!tFTbKLT zr@N82)OZ?u!XmqynYOfd9I$Mkwstn}wp^dK?c39Ai+$S8W0wx6hYxgGWxLXj&X#st z?b8_d*)W{$Yj3sMm``J63#>jCPs^TE&%(M%)WzYE(`W3(zvX8INaWsD(r%=+;ro6#HJlfo z7p@F%4RfAao-vHtT?2#6aXVp8Lkh`k)Mw8?-uvAxyJ*kar|miW3?#M;(*Gv(^^^IM z#hxJ2*~I6UrgSWu|dWhQ=ciNn`+eFdf@1Sb?o`hkqUH z4re=<9xO#EXA<@~mh)X$1HL@(F14YTZ?;qmzGM!I;#x&G+d_;is?c+__7L`tx(Mpe zSj`A(&N-DMsB;iH5kYN(xK0H13_|-Ns3GUBkDzWr*tH01#Td&7&d+&feKg3-=+*M*|&kv5?CuF2%Q!T9W-?usZA;2M@{)y@kcuW6-R)^=6x^zo0V)3HRq>>-nc1 z_AB+qAYof^o}N#c6J&|~7&WK{gW3)K))?@ruG)TrbR9H`Z!Fjo)!6Fq%3!(EVNWmvI_!8U zo+r!k)VKs!H&`a4_P`<;tdYSI8LW`O0vW82!Sa}u;hX_kHK*rKzVD5nITbdTSEHqj zw?Py>;CixW)bV_#bb3p`{S(LU)$w(s%HCDk=hbo;7C~jQaK5u^YLxm zkYw7>>$EcC^?QSCPy7l2c-$grz&3l_>aD@H+YZ=^oz?`Y{eR<#J9rn>23}7n>FNIi DB}TqP diff --git a/app/src/main/res/font/circular_std_book.otf b/app/src/main/res/font/circular_std_book.otf deleted file mode 100755 index 3a1f1ad82ef9ee891d7977cd5f6736404cb3afa1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68940 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 bm)u+K19{O;?hmegZf|)^1FfGBrz`&-FR{+x diff --git a/app/src/main/res/font/font.xml b/app/src/main/res/font/font.xml index 485b0418..4fe9ca2d 100644 --- a/app/src/main/res/font/font.xml +++ b/app/src/main/res/font/font.xml @@ -1,11 +1,11 @@ \ No newline at end of file diff --git a/app/src/main/res/font/sans_bold.ttf b/app/src/main/res/font/sans_bold.ttf new file mode 100755 index 0000000000000000000000000000000000000000..80497666ec8ca0abb81ce5bd9ce649896555753e GIT binary patch literal 117916 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 T0?uqfD&gf_PSWS(z4-k%e-4(l literal 0 HcmV?d00001 diff --git a/app/src/main/res/font/sans_regular.ttf b/app/src/main/res/font/sans_regular.ttf new file mode 100755 index 0000000000000000000000000000000000000000..ab605f9e2e3c347708a810435ba3a7debc52e1df GIT binary patch literal 119984 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 @@ -31,16 +31,20 @@ android:layout_height="match_parent" android:orientation="vertical"> - + + + + @@ -78,7 +82,7 @@ android:layout_marginEnd="96dp"> - - @@ -71,7 +66,7 @@ android:layout_height="match_parent"> @@ -42,7 +42,7 @@ android:layout_height="match_parent"> - - - - + tools:ignore="MissingPrefix" /> - + @@ -61,7 +61,7 @@ android:layout_weight="0"> diff --git a/app/src/main/res/layout-xlarge-land/fragment_home.xml b/app/src/main/res/layout-xlarge-land/fragment_home.xml index db53cbb0..6c216571 100644 --- a/app/src/main/res/layout-xlarge-land/fragment_home.xml +++ b/app/src/main/res/layout-xlarge-land/fragment_home.xml @@ -27,24 +27,19 @@ android:elevation="0dp" app:elevation="0dp"> - - - - + tools:ignore="MissingPrefix" /> - + diff --git a/app/src/main/res/layout-xlarge-land/fragment_player.xml b/app/src/main/res/layout-xlarge-land/fragment_player.xml index 9284e24d..68862dbd 100644 --- a/app/src/main/res/layout-xlarge-land/fragment_player.xml +++ b/app/src/main/res/layout-xlarge-land/fragment_player.xml @@ -8,7 +8,7 @@ android:focusable="true"> @@ -54,13 +54,12 @@ diff --git a/app/src/main/res/layout-xlarge/fragment_home.xml b/app/src/main/res/layout-xlarge/fragment_home.xml index 5bc9820c..76f220b8 100644 --- a/app/src/main/res/layout-xlarge/fragment_home.xml +++ b/app/src/main/res/layout-xlarge/fragment_home.xml @@ -28,24 +28,19 @@ android:elevation="0dp" app:elevation="0dp"> - - - - + tools:ignore="MissingPrefix" /> - + @@ -54,13 +54,12 @@ diff --git a/app/src/main/res/layout/fragment_adaptive_player.xml b/app/src/main/res/layout/fragment_adaptive_player.xml index c377147d..1150691a 100644 --- a/app/src/main/res/layout/fragment_adaptive_player.xml +++ b/app/src/main/res/layout/fragment_adaptive_player.xml @@ -34,7 +34,7 @@ @@ -69,7 +69,7 @@ android:layout_weight="0"> @@ -36,13 +36,19 @@ android:orientation="vertical"> + + + + + @@ -77,7 +82,7 @@ android:layout_gravity="bottom"> - + - - + - - - - - - - - - - - - - - - - - - - - - - - - + - + @@ -70,7 +67,7 @@ android:layout_height="match_parent"> - - - + + + + + + android:clickable="true" + android:focusable="true" + android:foreground="?rectSelector" + android:minHeight="72dp" + android:orientation="horizontal" + tools:ignore="UnusedAttribute"> - + android:layout_gravity="center_vertical" + android:layout_weight="0" + app:cardCornerRadius="6dp" + app:cardElevation="0dp" + app:contentPaddingLeft="16dp"> - + - + - - - - - - - - - - - - + android:layout_marginTop="12dp" + android:layout_marginBottom="12dp" + android:layout_weight="1.0" + android:orientation="vertical" + android:paddingStart="16dp" + android:paddingEnd="16dp"> - + + + + + diff --git a/app/src/main/res/layout/fragment_fit.xml b/app/src/main/res/layout/fragment_fit.xml index b45af0d6..2fddff91 100644 --- a/app/src/main/res/layout/fragment_fit.xml +++ b/app/src/main/res/layout/fragment_fit.xml @@ -14,13 +14,12 @@ android:orientation="vertical"> - - @@ -93,98 +88,7 @@ android:textColor="?android:attr/textColorSecondary" /> - + - - - - - - - - - - - - - - - - - - + diff --git a/app/src/main/res/layout/fragment_flat_player.xml b/app/src/main/res/layout/fragment_flat_player.xml index 7f3ef825..f24de9d1 100644 --- a/app/src/main/res/layout/fragment_flat_player.xml +++ b/app/src/main/res/layout/fragment_flat_player.xml @@ -9,7 +9,7 @@ android:orientation="vertical"> @@ -33,26 +33,24 @@ - - - - - - - + - - - - + diff --git a/app/src/main/res/layout/fragment_full.xml b/app/src/main/res/layout/fragment_full.xml index 0782b84b..0bc5d5c5 100644 --- a/app/src/main/res/layout/fragment_full.xml +++ b/app/src/main/res/layout/fragment_full.xml @@ -44,7 +44,7 @@ diff --git a/app/src/main/res/layout/fragment_hmm_player.xml b/app/src/main/res/layout/fragment_hmm_player.xml index b3d534a5..a65dde76 100644 --- a/app/src/main/res/layout/fragment_hmm_player.xml +++ b/app/src/main/res/layout/fragment_hmm_player.xml @@ -103,8 +103,7 @@ diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index c7649a14..4237b4c2 100755 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -27,13 +27,13 @@ - @@ -32,12 +32,11 @@ - - - - - - - - + - + - + - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/fragment_volume.xml b/app/src/main/res/layout/fragment_volume.xml index ebe69c00..456c394f 100755 --- a/app/src/main/res/layout/fragment_volume.xml +++ b/app/src/main/res/layout/fragment_volume.xml @@ -20,7 +20,7 @@ android:id="@+id/volumeSeekBar" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_weight="1" /> + android:layout_weight="1" /> - - + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index b7958152..a79edbc1 100755 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -105,7 +105,6 @@ @layout/activity_album - @layout/activity_album_style_2 @@ -201,7 +200,6 @@ @layout/activity_album - @layout/activity_album_small diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml deleted file mode 100644 index a6b3daec..00000000 --- a/app/src/main/res/values/ic_launcher_background.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/app/src/main/res/values/md_colors.xml b/app/src/main/res/values/md_colors.xml deleted file mode 100644 index 0d2c4cc4..00000000 --- a/app/src/main/res/values/md_colors.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ac68a315..a9fcb4c0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -558,4 +558,5 @@ Click or Slide Click to open with or slide to without transparent navigation of now playing screen Clear queue + Buy Retro Music Pro diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index bb7d822a..3a97910a 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -40,13 +40,10 @@ 16dp - - @@ -100,5 +99,11 @@ start + + diff --git a/app/src/main/res/values/swipe_button_attrs.xml b/app/src/main/res/values/swipe_button_attrs.xml deleted file mode 100644 index ecd20f74..00000000 --- a/app/src/main/res/values/swipe_button_attrs.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/sans/res/font/font.xml b/app/src/sans/res/font/font.xml index e4827093..00bae1c3 100644 --- a/app/src/sans/res/font/font.xml +++ b/app/src/sans/res/font/font.xml @@ -4,4 +4,8 @@ android:font="@font/product_sans_regular" android:fontStyle="normal" android:fontWeight="400" /> + \ No newline at end of file diff --git a/app/src/sans/res/font/product_sans_bold.ttf b/app/src/sans/res/font/product_sans_bold.ttf new file mode 100755 index 0000000000000000000000000000000000000000..d847195c7600a96700df62b68a5009be200f234d GIT binary patch literal 55548 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 R7%yccB}jfijK^r{{{Z