;
-}
+# OkHttp
+-keepattributes Signature
+-keepattributes *Annotation*
+-keep interface com.squareup.okhttp3.** { *; }
+-dontwarn com.squareup.okhttp3.**
--keep class !android.support.v7.internal.view.menu.**,** {*;}
+#-dontwarn
+#-ignorewarnings
--dontwarn
--ignorewarnings
+#Jaudiotagger
+-dontwarn org.jaudiotagger.**
+-dontwarn org.jcodec.**
+-keep class org.jaudiotagger.** { *; }
+-keep class org.jcodec.** { *; }
-# ------- FastScrollRecycleView START -------
--keep class com.simplecityapps.recyclerview_fastscroll.views.FastScrollPopup { *; }
-# ------- FastScrollRecycleView END -------
-
--keep public class android.support.design.widget.BottomNavigationView { *; }
--keep public class android.support.design.internal.BottomNavigationMenuView { *; }
--keep public class android.support.design.internal.BottomNavigationPresenter { *; }
--keep public class android.support.design.internal.BottomNavigationItemView { *; }
-
-#-dontwarn android.support.v8.renderscript.*
-#-keepclassmembers class android.support.v8.renderscript.RenderScript {
-# native *** rsn*(...);
-# native *** n*(...);
-#}
-
-#-keep class org.jaudiotagger.** { *; }
\ No newline at end of file
+-keepclassmembers enum * { *; }
+-keepattributes *Annotation*, Signature, Exception
+-keepnames class androidx.navigation.fragment.NavHostFragment
+-keep class * extends androidx.fragment.app.Fragment{}
+-keepnames class * extends android.os.Parcelable
+-keepnames class * extends java.io.Serializable
+-keep class code.name.monkey.retromusic.network.model.** { *; }
+-keep class code.name.monkey.retromusic.model.** { *; }
+-keep class com.google.android.material.bottomsheet.** { *; }
\ No newline at end of file
diff --git a/app/src/debug/res/font/bold.ttf b/app/src/debug/res/font/bold.ttf
new file mode 100644
index 000000000..96619df92
Binary files /dev/null and b/app/src/debug/res/font/bold.ttf differ
diff --git a/app/src/debug/res/font/google_sans_bold.ttf b/app/src/debug/res/font/google_sans_bold.ttf
new file mode 100644
index 000000000..80497666e
Binary files /dev/null and b/app/src/debug/res/font/google_sans_bold.ttf differ
diff --git a/app/src/debug/res/font/google_sans_medium.ttf b/app/src/debug/res/font/google_sans_medium.ttf
new file mode 100644
index 000000000..1543660da
Binary files /dev/null and b/app/src/debug/res/font/google_sans_medium.ttf differ
diff --git a/app/src/debug/res/font/google_sans_regular.ttf b/app/src/debug/res/font/google_sans_regular.ttf
new file mode 100644
index 000000000..ab605f9e2
Binary files /dev/null and b/app/src/debug/res/font/google_sans_regular.ttf differ
diff --git a/app/src/debug/res/font/medium.ttf b/app/src/debug/res/font/medium.ttf
new file mode 100644
index 000000000..fd818d6f5
Binary files /dev/null and b/app/src/debug/res/font/medium.ttf differ
diff --git a/app/src/debug/res/font/regular.ttf b/app/src/debug/res/font/regular.ttf
new file mode 100644
index 000000000..e2c69c3fb
Binary files /dev/null and b/app/src/debug/res/font/regular.ttf differ
diff --git a/app/src/debug/res/font/sans.xml b/app/src/debug/res/font/sans.xml
new file mode 100644
index 000000000..7bbc8513b
--- /dev/null
+++ b/app/src/debug/res/font/sans.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/debug/res/values/bools.xml b/app/src/debug/res/values/bools.xml
new file mode 100644
index 000000000..7d3f0ee62
--- /dev/null
+++ b/app/src/debug/res/values/bools.xml
@@ -0,0 +1,5 @@
+
+
+ true
+ false
+
\ No newline at end of file
diff --git a/app/src/debug/res/values/donottranslate.xml b/app/src/debug/res/values/donottranslate.xml
new file mode 100644
index 000000000..79e559838
--- /dev/null
+++ b/app/src/debug/res/values/donottranslate.xml
@@ -0,0 +1,4 @@
+
+
+ Metro Debug
+
\ No newline at end of file
diff --git a/app/src/debug/res/values/styles.xml b/app/src/debug/res/values/styles.xml
new file mode 100644
index 000000000..f3761b219
--- /dev/null
+++ b/app/src/debug/res/values/styles.xml
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5fa8503e1..14fbaaab7 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,31 +1,57 @@
+ package="code.name.monkey.retromusic"
+ android:installLocation="auto">
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
+
+ android:theme="@style/Theme.RetroMusic.FollowSystem"
+ android:usesCleartextTraffic="true"
+ tools:ignore="UnusedAttribute">
+ android:name=".activities.MainActivity"
+ android:exported="true"
+ android:launchMode="singleTop"
+ android:theme="@style/Theme.RetroMusic.SplashScreen">
@@ -36,7 +62,6 @@
-
@@ -73,7 +98,7 @@
-
+
@@ -96,74 +121,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
+ android:name=".activities.saf.SAFGuideActivity"
+ android:theme="@style/Theme.Intro" />
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
@@ -173,9 +226,10 @@
android:name="android.appwidget.provider"
android:resource="@xml/app_widget_big_info" />
+
@@ -187,7 +241,7 @@
@@ -197,9 +251,21 @@
android:name="android.appwidget.provider"
android:resource="@xml/app_widget_small_info" />
+
+
+
+
+
+
+
@@ -209,36 +275,76 @@
android:name="android.appwidget.provider"
android:resource="@xml/app_widget_card_info" />
-
+
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
-
-
+ android:name="autoStoreLocales"
+ android:value="true" />
+
-
\ No newline at end of file
+
+
+
+
+
+
diff --git a/app/src/main/assets/fonts/circular_std_black.otf b/app/src/main/assets/fonts/circular_std_black.otf
deleted file mode 100755
index c62b210c5..000000000
Binary files a/app/src/main/assets/fonts/circular_std_black.otf and /dev/null differ
diff --git a/app/src/main/assets/fonts/circular_std_book.otf b/app/src/main/assets/fonts/circular_std_book.otf
deleted file mode 100755
index 3a1f1ad82..000000000
Binary files a/app/src/main/assets/fonts/circular_std_book.otf and /dev/null differ
diff --git a/app/src/main/assets/fonts/product_sans_bold.ttf b/app/src/main/assets/fonts/product_sans_bold.ttf
deleted file mode 100755
index d847195c7..000000000
Binary files a/app/src/main/assets/fonts/product_sans_bold.ttf and /dev/null differ
diff --git a/app/src/main/assets/fonts/product_sans_regular.ttf b/app/src/main/assets/fonts/product_sans_regular.ttf
deleted file mode 100755
index c0442ee29..000000000
Binary files a/app/src/main/assets/fonts/product_sans_regular.ttf and /dev/null differ
diff --git a/app/src/main/assets/images/daksh.png b/app/src/main/assets/images/daksh.png
new file mode 100644
index 000000000..737f9066c
Binary files /dev/null and b/app/src/main/assets/images/daksh.png differ
diff --git a/app/src/main/assets/images/haythem.jpg b/app/src/main/assets/images/haythem.jpg
new file mode 100644
index 000000000..c74527f1d
Binary files /dev/null and b/app/src/main/assets/images/haythem.jpg differ
diff --git a/app/src/main/assets/images/hemanth.jpg b/app/src/main/assets/images/hemanth.jpg
new file mode 100644
index 000000000..b8f5e7805
Binary files /dev/null and b/app/src/main/assets/images/hemanth.jpg differ
diff --git a/app/src/main/assets/images/lenny.jpg b/app/src/main/assets/images/lenny.jpg
new file mode 100644
index 000000000..7f8025f08
Binary files /dev/null and b/app/src/main/assets/images/lenny.jpg differ
diff --git a/app/src/main/assets/images/milind.png b/app/src/main/assets/images/milind.png
new file mode 100644
index 000000000..bbf9b8278
Binary files /dev/null and b/app/src/main/assets/images/milind.png differ
diff --git a/app/src/main/assets/images/pratham.jpg b/app/src/main/assets/images/pratham.jpg
new file mode 100644
index 000000000..7f05ad9d5
Binary files /dev/null and b/app/src/main/assets/images/pratham.jpg differ
diff --git a/app/src/main/assets/index.html b/app/src/main/assets/index.html
deleted file mode 100644
index 115106cb5..000000000
--- a/app/src/main/assets/index.html
+++ /dev/null
@@ -1,57 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- Phonograph by Karim Abou Zeid
- RxAndroid by RxAndroid authors
- RxJava by RxJava authors
- Material Dialogs by Aidan Michael Follestad
- Calligraphy by RxJava authors
- Android-Snowfall by JetRadar
- Android Sliding Up Panel by The Umano Team
- AOSP Support Libraries by AOSP contributors
- Butter Knife by Jake Wharton
- Glide by Sam Judd
- Retrofit by Square team
- Material Contextual Action Bar by Aidan Michael Follestad
- OkHttp by Square team
- CircleImageView by Henning Dodenhof
- Transitions Everywhere by Henning Dodenhof
- MaterialProgressBar by Zhang Hai
- Android In-App Billing v3 Library by Henning Dodenhof
- Advanced RecyclerView by Haruki Hasegawa
- Android-ObservableScrollView by Soichiro Kashima
- BottomNavigationViewEx by Ittianyu
- Swipe-Button by EBANX Team
- Font used(CIRCULAR STD BOOK FONT) by NIELSON CAETANO
- Icons by Austin Andrews
- Croller by Harjot Oberai
- Material Design City Wallpaper
-
-
-
diff --git a/app/src/main/assets/license.html b/app/src/main/assets/license.html
new file mode 100644
index 000000000..2fd8ba5ed
--- /dev/null
+++ b/app/src/main/assets/license.html
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+ Phonograph by
+ Karim Abou Zeid
+AOSP Support Libraries by AOSP contributors
+ Glide by Sam Judd
+ Retrofit by Square team
+
+ OkHttp by Square team
+Koin by Arnaud Giuliani
+ Material Dialogs and Cab
+ by Aidan Michael Follestad
+
+ Material Contextual Action Bar by Aidan Michael Follestad
+ Advanced RecyclerView by Haruki Hasegawa
+Custom Activity on Crash by Eduard Ereza Martínez
+
+NanoHttpd by NanoHttpd Team
+Circular Seekbar by Tankery
+jAudioTagger by Kanedias
+Android Fast Scroll by Zhang Hai
+Image Picker by Dhaval Patel
+Material Intro by Jan Heinrich Reimer
+Slidr by Drew Heavner
+FadingEdgeLayout by bosphere
+KeyboardVisibilityEvent by Yasuhiro SHIMIZU
+android-snowfall by Jetradar Mobile
+Insetter by Chris Banes
+ Icons by Austin Andrews
+ Material Design City Wallpaper
+
+
+
diff --git a/app/src/main/assets/retro-changelog.html b/app/src/main/assets/retro-changelog.html
new file mode 100644
index 000000000..b6b1c411a
--- /dev/null
+++ b/app/src/main/assets/retro-changelog.html
@@ -0,0 +1,466 @@
+
+
+
+
+
+
+
+
+
+
March 30, 2023
+
v6.1.0
+
What's New
+
+ App now targets Android 13, support for Granular media permissions, Photo picker, Per-app language preferences & Predictive back gesture
+
+
Fixed
+
+ Fixed playlist reordering crash
+ Other minor bugs fixes and improvements
+
+
+
+
March 13, 2023
+
v6.0.4
+
What's New
+
+ Minor redesign in Playlist details screen
+ Updated translations
+
+
Fixed
+
+ Fixed file popup menu actions in Folders tab
+ Fixed playlist image loading
+ Fixed blurry album art in Android 13
+ Minor bug fixes and improvements
+
+
+
+
July 10, 2022
+
v6.0.3Beta
+
Fixed
+
+ Migrated icons to Material symbols
+
+
+
+
June 21, 2022
+
v6.0.2Beta
+
Fixed
+
+ Minor bug fixes and improvements
+
+
+
+
June 13, 2022
+
v6.0.1Beta
+
Fixed
+
+ Fixed ChromeCast crash
+ Fixed Slider crashes
+ Fixed storage related crashes on Android 10
+ Fixed CrossFade not working Fade Audio is not working
+
+
+
+
June 7, 2022
+
v6.0.0Beta
+
What's New
+
+ Better Cast
+ Mini player in settings screen
+ Changed Seekbar with Sliders
+ Added NavigationRailView for Landscape
+ Show remaining time in Sleep timer dialog
+
+
Fixed
+
+ Fixed Top/Recent Artists/Albums not updating (Wrong sort order)
+ Fixed all Blacklist related crashes
+ Fix restart button not working in crash activity
+
+
+
+
May 25, 2022
+
v5.8.5
+
What's New
+
+ Display song images along in the artist and album details pages
+ Removed the Internet permissions and all the associated features
+
+
Fixed
+
+ Fixed crashing occurs during changing screen orientation
+
+
+
+
May 13, 2022
+
v5.8.4
+
What's New
+
+ Added a toggle to enable/disable swipe down to dismiss mini player
+
+
Fixed
+
+ Fixed Playback speed and pitch not working when CrossFade is enabled
+ Fixed crash when adding folders to blacklist
+ Fix bugs in MD3 theme
+
+
+
+
May 07, 2022
+
v5.8.3
+
What's New
+
+ Added a new MD3 now playing theme
+ Swipe down to dismiss Mini player
+ Add support for Just Black with Material You
+
+
Fixed
+
+ Fixed sharing of files from SD Card
+ Fix ChromeCast crash and bugs
+ Fix Audio Crossfade
+ Tried to fix incorrect song data in notification
+
+
+
+
April 8, 2022
+
v5.8.0
+
What's New
+
+
Fixed
+
+ Fixed Classic Notification crash
+ Fixed crash when clicking on Playlist in the Search Tab
+ Fixed settings change not reflecting immediately
+ Fixed shuffle
+ Fixed Song duration not visible in Card & Blur card themes
+ Fixed some Album skip styles
+ Minor bug fixes & UI improvements
+
+
+
+
March 13, 2022
+
v5.7.3
+
What's New
+
+ Added adaptive color in Material now playing theme
+ Added an option to share crash report
+ Added options to clear, pause history
+
+
Fixed
+
+ Adapt Wallpaper accent for better readability
+ Optimized search
+ Made sleep timer precise
+
+
+
+
February 13, 2022
+
v5.7.2Beta
+
What's New
+
+ Animated splash screen on Android 12 devices
+
+
Fixed
+
+ Fixed crash when removing song from Playing queue
+ Fixed lyrics editing crash
+ Fixed shuffle button not working
+ Fixed crash on song deletion
+
+
+
+
February 1, 2022
+
v5.7.1Beta
+
What's New
+
+ Added option to disable changing song by swiping anywhere on the now playing screen
+
+
Fixed
+
+ Fixed Playlist save on A11+
+ Fixed Just Black theme
+ Fixed some UI issues
+
+
Improved
+
+ Improvements to search
+
+
+
+
January 25, 2022
+
v5.7.0Beta
+
What's New
+
+ Added Android Auto
+ Added accent color extraction on Android 8.1+ devices
+ Added new Circle widget
+ Added Collapsing appbar to library tabs with an option to switch back to simple appbar
+
+ Added Search tab
+ Added option to use circular play button
+ Added lyrics editing on A11+ devices again
+ Added Long Press to forward, rewind current song
+ Added ability to set Playback speed and pitch
+ Added option to show lyrics over Cover
+ Added option to keep screen on when showing lyrics
+ Added option to switch to Manrope font
+
+
Fixed
+
+ Fixed Gapless Playback
+ Fixed Shuffle FAB going behind Mini Player
+ Fixed crashes on Pre-marshmallow devices
+ Blacklisted songs can't be played after opening from outside the app
+ Fixed various small bugs and some minor improvements
+
+
+
+
January 1, 2021
+
v5.6.1
+
Fixed
+
+ Fixed artist covers not updating and showing album cover images.
+ Fixed FAB's not visible (Shuffle, Save, etc.)
+ Fixed a crash when a Song is deleted in Artist Details
+ Fixed Snowfall effect
+ Fixed empty notification when queue is cleared
+
+
+
+
December 25, 2021
+
v5.6.0Beta
+
What's New
+
+ Added Artwork editing for songs
+ Circle theme has album art now
+ Upgraded tag editor library, we should support reading tags of more formats now e.g.
+ opus.
+
+ Added Snowfall effect
+ Crossfade effect for background when Song is changed for Blur, Blur card themes
+ Album art is hidden when Show lyrics is enabled
+ Added fastscroll in Playlists tab
+ Added Chooser to choose what to restore
+ Hide BottomNavigation when only one tab is selected in Library Categories(This was
+ already there but when changing screens it was getting visible)
+
+
+
Fixed
+
+ Fixed Ringtone crash
+ Fixed blank album cover bug
+ Fixed bottom navigation visible in Playing Queue
+ Fixed lockscreen dragging glitch
+ Fixed incorrect colors when no cover art is available
+ Fixed favorite not updating when song is changed
+ Fixed playlist not getting created & playlist creation crash with same name
+ Fixed a bug in "Plain" Now playing theme where onClick event is consumed by the views
+ behind the bottom sheet
+
+
+
+
+
December 6, 2021
+
v5.4.2Beta
+
Fixed
+
+
+
+
December 1, 2021
+
v5.4.1Beta
+
What's New
+
+ Added file selection from system file picker for restore
+ Added Grid size selection for Playlists
+
+
Improved
+
+ Enable Material You by default on Android 12
+ Reworked Grid Style saving
+ Improved Playlist preview images
+ UI improvements
+
+
Improved
+
+ Fixed File deletion on Android 10
+ Fixed Sleep Timer crash
+ Fixed Bottom Toolbar not clickable in now playing when Shuffle is clicked
+ Fixed Album Artist sort order
+ Fixed a crash when clicking the "Clear All" button in the Blacklist folder selection
+
+ Fixed Continuous crashes on A12 when the song ends
+ Fixed New Music Mix multiple clicks crash
+
+
+
+
November 22, 2021
+
v5.4.0Beta
+
What's New
+
+ Material You
+ Going Edge-to-Edge
+ Added Backup & restore
+
+
Improved
+
+ Improved Animations
+ Improved Crossfade
+
+
+
+
September 06, 2021
+
v5.0.0
+
What's New
+
+ Added ability to Remember last tab
+ Added Whitelisting songs
+ You can now browse SDCard from Folders Tab
+
+
+
+
August 22, 2021
+
v4.4.0Beta
+
What's New
+
+ Added Crossfade
+ Multi-select in Album and Artist Details
+ Albums now show Album Artists instead of artist of first song
+
+
Fixed
+
+ Fixed Playlist Preview Images
+
+
+
+
August 11, 2021
+
v4.2.30Beta
+
What's New
+
+ Revamped Playlist Tab
+ Revamped Genres Tab
+ Added support for embedded Synced Lyrics
+ Added animated icons
+
+
Fixed
+
+ Fixed Language Switching
+ Fixed some reported bugs
+
+
+
+
July 18, 2021
+
v4.2.020Beta
+
What's New
+
+ Added ChromeCast Support
+ Added Lyrics Editor for Normal and Synced Lyrics
+ Added Ripple Animation for Color Theme
+ Added Drag to seek in Tiny Theme
+ Added Playlist Order
+ Added Search Filters
+ Added Audio Fade
+ Synced Lyrics in all Themes
+ Swipe anywhere to change song
+
+
Improved
+
+ Fixed Navigate by Album Artist
+ Changed New Music Mix Actions
+ Improved Animations
+ And some minor bug fixes and improvements
+
+
+
+
October 12, 2020
+
v4.0.10
+
What's New
+
+ Re-built from scratch using MVVM Architecture and JetPack Components
+ New Material Design icon
+ Implemented a custom database for playlists
+ Added new Material Design motions
+ Bug fixes & performance improvements
+ Revamped Home tab UI
+ Android 11 support
+ And more!
+
+
+
+
April 30, 2020
+
v3.5.110Beta
+
What's New
+
+ Changed profile form image to icon
+ New what's new screen
+ Added In-App language changer, where you can select language
+
+
Improved
+
+ Improved loading of Songs, Albums, Artists, Genres, Playlists
+
+
+
\ No newline at end of file
diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png
new file mode 100644
index 000000000..aa6e4ae9a
Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ
diff --git a/app/src/main/ic_launcher-web.png b/app/src/main/ic_launcher-web.png
index 924099d54..7e7647db5 100644
Binary files a/app/src/main/ic_launcher-web.png and b/app/src/main/ic_launcher-web.png differ
diff --git a/app/src/main/java/code/name/monkey/retromusic/App.kt b/app/src/main/java/code/name/monkey/retromusic/App.kt
new file mode 100644
index 000000000..93dfe4d96
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/App.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic
+
+import android.app.Application
+import androidx.preference.PreferenceManager
+import cat.ereza.customactivityoncrash.config.CaocConfig
+import code.name.monkey.appthemehelper.ThemeStore
+import code.name.monkey.appthemehelper.util.VersionUtils
+import code.name.monkey.retromusic.activities.ErrorActivity
+import code.name.monkey.retromusic.activities.MainActivity
+import code.name.monkey.retromusic.appshortcuts.DynamicShortcutManager
+import code.name.monkey.retromusic.helper.WallpaperAccentManager
+import org.koin.android.ext.koin.androidContext
+import org.koin.core.context.startKoin
+
+class App : Application() {
+
+ private val wallpaperAccentManager = WallpaperAccentManager(this)
+
+ override fun onCreate() {
+ super.onCreate()
+ instance = this
+
+ startKoin {
+ androidContext(this@App)
+ modules(appModules)
+ }
+ // default theme
+ if (!ThemeStore.isConfigured(this, 3)) {
+ ThemeStore.editTheme(this)
+ .accentColorRes(code.name.monkey.appthemehelper.R.color.md_deep_purple_A200)
+ .coloredNavigationBar(true)
+ .commit()
+ }
+ wallpaperAccentManager.init()
+
+ if (VersionUtils.hasNougatMR())
+ DynamicShortcutManager(this).initDynamicShortcuts()
+
+ // setting Error activity
+ CaocConfig.Builder.create().errorActivity(ErrorActivity::class.java)
+ .restartActivity(MainActivity::class.java).apply()
+
+ // Set Default values for now playing preferences
+ // This will reduce startup time for now playing settings fragment as Preference listener of AbsSlidingMusicPanelActivity won't be called
+ PreferenceManager.setDefaultValues(this, R.xml.pref_now_playing_screen, false)
+ }
+
+ override fun onTerminate() {
+ super.onTerminate()
+ wallpaperAccentManager.release()
+ }
+
+ companion object {
+ private var instance: App? = null
+
+ fun getContext(): App {
+ return instance!!
+ }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/Constants.java b/app/src/main/java/code/name/monkey/retromusic/Constants.java
deleted file mode 100644
index c8485a4df..000000000
--- a/app/src/main/java/code/name/monkey/retromusic/Constants.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package code.name.monkey.retromusic;
-
-public class Constants {
-
- public static final String DISCORD_LINK = "https://discord.gg/qTecXXn";
-
- public static final String RETRO_MUSIC_PACKAGE_NAME = "code.name.monkey.retromusic";
- public static final String MUSIC_PACKAGE_NAME = "com.android.music";
- public static final String ACTION_TOGGLE_PAUSE = RETRO_MUSIC_PACKAGE_NAME + ".togglepause";
- public static final String ACTION_PLAY = RETRO_MUSIC_PACKAGE_NAME + ".play";
- public static final String ACTION_PLAY_PLAYLIST = RETRO_MUSIC_PACKAGE_NAME + ".play.playlist";
- public static final String ACTION_PAUSE = RETRO_MUSIC_PACKAGE_NAME + ".pause";
- public static final String ACTION_STOP = RETRO_MUSIC_PACKAGE_NAME + ".stop";
- public static final String ACTION_SKIP = RETRO_MUSIC_PACKAGE_NAME + ".skip";
- public static final String ACTION_REWIND = RETRO_MUSIC_PACKAGE_NAME + ".rewind";
- public static final String ACTION_QUIT = RETRO_MUSIC_PACKAGE_NAME + ".quitservice";
- public static final String INTENT_EXTRA_PLAYLIST =
- RETRO_MUSIC_PACKAGE_NAME + "intentextra.playlist";
- public static final String INTENT_EXTRA_SHUFFLE_MODE =
- RETRO_MUSIC_PACKAGE_NAME + ".intentextra.shufflemode";
- public static final String APP_WIDGET_UPDATE = RETRO_MUSIC_PACKAGE_NAME + ".appwidgetupdate";
- public static final String EXTRA_APP_WIDGET_NAME = RETRO_MUSIC_PACKAGE_NAME + "app_widget_name";
- // do not change these three strings as it will break support with other apps (e.g. last.fm scrobbling)
- public static final String META_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".metachanged";
- public static final String QUEUE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".queuechanged";
- public static final String PLAY_STATE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".playstatechanged";
- public static final String REPEAT_MODE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".repeatmodechanged";
- public static final String SHUFFLE_MODE_CHANGED =
- RETRO_MUSIC_PACKAGE_NAME + ".shufflemodechanged";
- public static final String MEDIA_STORE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".mediastorechanged";
- public static final String RATE_ON_GOOGLE_PLAY = "https://play.google.com/store/apps/details?id=code.name.monkey.retromusic";
- public static final String PAYPAL_ME_URL = "https://www.paypal.me/h4h14";
- public static final String GOOGLE_PLUS_COMMUNITY = "https://plus.google.com/communities/110811566242871492162";
- public static final String TRANSLATE = "http://monkeycodeapp.oneskyapp.com/collaboration/project?id=238534";
- public static final String GITHUB_PROJECT = "https://github.com/h4h13/RetroMusicPlayer";
- public static final String BASE_API_URL_KUGOU = "http://lyrics.kugou.com/";
- public static final String TELEGRAM_CHANGE_LOG = "https://t.me/retromusiclog";
- public static final String USER_PROFILE = "profile.jpg";
- public static final String USER_BANNER = "banner.jpg";
- public static final String APP_INSTAGRAM_LINK = "https://www.instagram.com/retromusicapp/";
- public static final String APP_TELEGRAM_LINK = "https://t.me/retromusicapp/";
- public static final String APP_TWITTER_LINK = "https://twitter.com/retromusicapp";
- public static final String FAQ_LINK = "https://github.com/h4h13/RetroMusicPlayer/blob/master/FAQ.md";
-}
diff --git a/app/src/main/java/code/name/monkey/retromusic/Constants.kt b/app/src/main/java/code/name/monkey/retromusic/Constants.kt
new file mode 100644
index 000000000..31032f62b
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/Constants.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic
+
+import android.provider.BaseColumns
+import android.provider.MediaStore
+
+object Constants {
+ const val TRANSLATE = "https://crowdin.com/project/retromusicplayer"
+ const val GITHUB_PROJECT = "https://github.com/MuntashirAkon/Metro"
+ const val TELEGRAM_CHANGE_LOG = "https://t.me/AppManagerChannel"
+ const val USER_PROFILE = "profile.jpg"
+ const val USER_BANNER = "banner.jpg"
+ const val FAQ_LINK = "https://github.com/MuntashirAkon/Metro/blob/master/FAQ.md"
+
+ const val IS_MUSIC =
+ MediaStore.Audio.AudioColumns.IS_MUSIC + "=1" + " AND " + MediaStore.Audio.AudioColumns.TITLE + " != ''"
+
+ const val DATA = "_data"
+
+ @Suppress("Deprecation")
+ val baseProjection = arrayOf(
+ BaseColumns._ID, // 0
+ MediaStore.Audio.AudioColumns.TITLE, // 1
+ MediaStore.Audio.AudioColumns.TRACK, // 2
+ MediaStore.Audio.AudioColumns.YEAR, // 3
+ MediaStore.Audio.AudioColumns.DURATION, // 4
+ DATA, // 5
+ MediaStore.Audio.AudioColumns.DATE_MODIFIED, // 6
+ MediaStore.Audio.AudioColumns.ALBUM_ID, // 7
+ MediaStore.Audio.AudioColumns.ALBUM, // 8
+ MediaStore.Audio.AudioColumns.ARTIST_ID, // 9
+ MediaStore.Audio.AudioColumns.ARTIST, // 10
+ MediaStore.Audio.AudioColumns.COMPOSER, // 11
+ ALBUM_ARTIST // 12
+ )
+ const val NUMBER_OF_TOP_TRACKS = 99
+}
+
+const val EXTRA_PLAYLIST_TYPE = "type"
+const val EXTRA_GENRE = "extra_genre"
+const val EXTRA_PLAYLIST = "extra_playlist"
+const val EXTRA_PLAYLIST_ID = "extra_playlist_id"
+const val EXTRA_ALBUM_ID = "extra_album_id"
+const val EXTRA_ARTIST_ID = "extra_artist_id"
+const val EXTRA_SONG = "extra_songs"
+const val EXTRA_PLAYLISTS = "extra_playlists"
+const val LIBRARY_CATEGORIES = "library_categories"
+const val EXTRA_SONG_INFO = "extra_song_info"
+const val DESATURATED_COLOR = "desaturated_color"
+const val BLACK_THEME = "black_theme"
+const val KEEP_SCREEN_ON = "keep_screen_on"
+const val TOGGLE_HOME_BANNER = "toggle_home_banner"
+const val NOW_PLAYING_SCREEN_ID = "now_playing_screen_id"
+const val CAROUSEL_EFFECT = "carousel_effect"
+const val COLORED_NOTIFICATION = "colored_notification"
+const val CLASSIC_NOTIFICATION = "classic_notification"
+const val GAP_LESS_PLAYBACK = "gapless_playback"
+const val ALBUM_ART_ON_LOCK_SCREEN = "album_art_on_lock_screen"
+const val BLURRED_ALBUM_ART = "blurred_album_art"
+const val NEW_BLUR_AMOUNT = "new_blur_amount"
+const val TOGGLE_HEADSET = "toggle_headset"
+const val GENERAL_THEME = "general_theme"
+const val ACCENT_COLOR = "accent_color"
+const val SHOULD_COLOR_APP_SHORTCUTS = "should_color_app_shortcuts"
+const val CIRCULAR_ALBUM_ART = "circular_album_art"
+const val USER_NAME = "user_name"
+const val TOGGLE_FULL_SCREEN = "toggle_full_screen"
+const val TOGGLE_VOLUME = "toggle_volume"
+const val ADAPTIVE_COLOR_APP = "adaptive_color_app"
+const val HOME_ARTIST_GRID_STYLE = "home_artist_grid_style"
+const val HOME_ALBUM_GRID_STYLE = "home_album_grid_style"
+const val TOGGLE_ADD_CONTROLS = "toggle_add_controls"
+const val ALBUM_COVER_STYLE = "album_cover_style_id"
+const val ALBUM_COVER_TRANSFORM = "album_cover_transform"
+const val TAB_TEXT_MODE = "tab_text_mode"
+const val LANGUAGE_NAME = "language_name"
+const val LOCALE_AUTO_STORE_ENABLED = "locale_auto_store_enabled"
+const val SLEEP_TIMER_FINISH_SONG = "sleep_timer_finish_song"
+const val ALBUM_GRID_STYLE = "album_grid_style_home"
+const val ARTIST_GRID_STYLE = "artist_grid_style_home"
+const val SAF_SDCARD_URI = "saf_sdcard_uri"
+const val SONG_SORT_ORDER = "song_sort_order"
+const val SONG_GRID_SIZE = "song_grid_size"
+const val GENRE_SORT_ORDER = "genre_sort_order"
+const val BLUETOOTH_PLAYBACK = "bluetooth_playback"
+const val INITIALIZED_BLACKLIST = "initialized_blacklist"
+const val ARTIST_SORT_ORDER = "artist_sort_order"
+const val ARTIST_ALBUM_SORT_ORDER = "artist_album_sort_order"
+const val ALBUM_SORT_ORDER = "album_sort_order"
+const val PLAYLIST_SORT_ORDER = "playlist_sort_order"
+const val ALBUM_SONG_SORT_ORDER = "album_song_sort_order"
+const val ARTIST_SONG_SORT_ORDER = "artist_song_sort_order"
+const val ALBUM_GRID_SIZE = "album_grid_size"
+const val ALBUM_GRID_SIZE_LAND = "album_grid_size_land"
+const val SONG_GRID_SIZE_LAND = "song_grid_size_land"
+const val ARTIST_GRID_SIZE = "artist_grid_size"
+const val ARTIST_GRID_SIZE_LAND = "artist_grid_size_land"
+const val PLAYLIST_GRID_SIZE = "playlist_grid_size"
+const val PLAYLIST_GRID_SIZE_LAND = "playlist_grid_size_land"
+const val COLORED_APP_SHORTCUTS = "colored_app_shortcuts"
+const val LAST_ADDED_CUTOFF = "last_added_interval"
+const val LAST_SLEEP_TIMER_VALUE = "last_sleep_timer_value"
+const val NEXT_SLEEP_TIMER_ELAPSED_REALTIME = "next_sleep_timer_elapsed_real_time"
+const val IGNORE_MEDIA_STORE_ARTWORK = "ignore_media_store_artwork"
+const val LAST_CHANGELOG_VERSION = "last_changelog_version"
+const val START_DIRECTORY = "start_directory"
+const val RECENTLY_PLAYED_CUTOFF = "recently_played_interval"
+const val LOCK_SCREEN = "lock_screen"
+const val ALBUM_ARTISTS_ONLY = "album_artists_only"
+const val ALBUM_ARTIST = "album_artist"
+const val ALBUM_DETAIL_SONG_SORT_ORDER = "album_detail_song_sort_order"
+const val ARTIST_DETAIL_SONG_SORT_ORDER = "artist_detail_song_sort_order"
+const val LYRICS_OPTIONS = "lyrics_tab_position"
+const val CHOOSE_EQUALIZER = "choose_equalizer"
+const val EQUALIZER = "equalizer"
+const val SONG_GRID_STYLE = "song_grid_style"
+const val PAUSE_ON_ZERO_VOLUME = "pause_on_zero_volume"
+const val FILTER_SONG = "filter_song"
+const val EXPAND_NOW_PLAYING_PANEL = "expand_now_playing_panel"
+const val EXTRA_ARTIST_NAME = "extra_artist_name"
+const val TOGGLE_SUGGESTIONS = "toggle_suggestions"
+const val AUDIO_FADE_DURATION = "audio_fade_duration"
+const val CROSS_FADE_DURATION = "cross_fade_duration"
+const val SHOW_LYRICS = "show_lyrics"
+const val REMEMBER_LAST_TAB = "remember_last_tab"
+const val LAST_USED_TAB = "last_used_tab"
+const val WHITELIST_MUSIC = "whitelist_music"
+const val MATERIAL_YOU = "material_you"
+const val SNOWFALL = "snowfall"
+const val LYRICS_TYPE = "lyrics_type"
+const val PLAYBACK_SPEED = "playback_speed"
+const val PLAYBACK_PITCH = "playback_pitch"
+const val CUSTOM_FONT = "custom_font"
+const val APPBAR_MODE = "appbar_mode"
+const val WALLPAPER_ACCENT = "wallpaper_accent"
+const val SCREEN_ON_LYRICS = "screen_on_lyrics"
+const val CIRCLE_PLAY_BUTTON = "circle_play_button"
+const val SWIPE_ANYWHERE_NOW_PLAYING = "swipe_anywhere_now_playing"
+const val PAUSE_HISTORY = "pause_history"
+const val MANAGE_AUDIO_FOCUS = "manage_audio_focus"
+const val SWIPE_DOWN_DISMISS = "swipe_to_dismiss"
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/HomeSection.kt b/app/src/main/java/code/name/monkey/retromusic/HomeSection.kt
new file mode 100644
index 000000000..4f1273a03
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/HomeSection.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic
+
+import androidx.annotation.IntDef
+
+@IntDef(
+ RECENT_ALBUMS,
+ TOP_ALBUMS,
+ RECENT_ARTISTS,
+ TOP_ARTISTS,
+ SUGGESTIONS,
+ FAVOURITES,
+ GENRES,
+ PLAYLISTS
+)
+@Retention(AnnotationRetention.SOURCE)
+annotation class HomeSection
+
+const val RECENT_ALBUMS = 3
+const val TOP_ALBUMS = 1
+const val RECENT_ARTISTS = 2
+const val TOP_ARTISTS = 0
+const val SUGGESTIONS = 5
+const val FAVOURITES = 4
+const val GENRES = 6
+const val PLAYLISTS = 7
+const val HISTORY_PLAYLIST = 8
+const val LAST_ADDED_PLAYLIST = 9
+const val TOP_PLAYED_PLAYLIST = 10
diff --git a/app/src/main/java/code/name/monkey/retromusic/Injection.java b/app/src/main/java/code/name/monkey/retromusic/Injection.java
deleted file mode 100644
index 481613ff2..000000000
--- a/app/src/main/java/code/name/monkey/retromusic/Injection.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package code.name.monkey.retromusic;
-
-import code.name.monkey.retromusic.providers.RepositoryImpl;
-import code.name.monkey.retromusic.providers.interfaces.Repository;
-import code.name.monkey.retromusic.rest.KogouClient;
-import code.name.monkey.retromusic.rest.service.KuGouApiService;
-import code.name.monkey.retromusic.util.schedulers.BaseSchedulerProvider;
-import code.name.monkey.retromusic.util.schedulers.SchedulerProvider;
-
-public class Injection {
-
- public static Repository provideRepository() {
- return RepositoryImpl.getInstance();
- }
-
- public static BaseSchedulerProvider provideSchedulerProvider() {
- return SchedulerProvider.getInstance();
- }
-
- public static KuGouApiService provideKuGouApiService() {
- return new KogouClient().getApiService();
- }
-}
diff --git a/app/src/main/java/code/name/monkey/retromusic/MainModule.kt b/app/src/main/java/code/name/monkey/retromusic/MainModule.kt
new file mode 100644
index 000000000..d88f8a760
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/MainModule.kt
@@ -0,0 +1,156 @@
+package code.name.monkey.retromusic
+
+import androidx.room.Room
+import code.name.monkey.retromusic.auto.AutoMusicProvider
+import code.name.monkey.retromusic.db.MIGRATION_23_24
+import code.name.monkey.retromusic.db.RetroDatabase
+import code.name.monkey.retromusic.fragments.LibraryViewModel
+import code.name.monkey.retromusic.fragments.albums.AlbumDetailsViewModel
+import code.name.monkey.retromusic.fragments.artists.ArtistDetailsViewModel
+import code.name.monkey.retromusic.fragments.genres.GenreDetailsViewModel
+import code.name.monkey.retromusic.fragments.playlists.PlaylistDetailsViewModel
+import code.name.monkey.retromusic.model.Genre
+import code.name.monkey.retromusic.repository.*
+import org.koin.android.ext.koin.androidContext
+import org.koin.androidx.viewmodel.dsl.viewModel
+import org.koin.dsl.bind
+import org.koin.dsl.module
+
+private val roomModule = module {
+
+ single {
+ Room.databaseBuilder(androidContext(), RetroDatabase::class.java, "playlist.db")
+ .addMigrations(MIGRATION_23_24)
+ .build()
+ }
+
+ factory {
+ get().playlistDao()
+ }
+
+ factory {
+ get().playCountDao()
+ }
+
+ factory {
+ get().historyDao()
+ }
+
+ single {
+ RealRoomRepository(get(), get(), get())
+ } bind RoomRepository::class
+}
+private val autoModule = module {
+ single {
+ AutoMusicProvider(
+ androidContext(),
+ get(),
+ get(),
+ get(),
+ get(),
+ get(),
+ get()
+ )
+ }
+}
+private val mainModule = module {
+ single {
+ androidContext().contentResolver
+ }
+}
+private val dataModule = module {
+ single {
+ RealRepository(
+ get(),
+ get(),
+ get(),
+ get(),
+ get(),
+ get(),
+ get(),
+ get(),
+ get(),
+ get(),
+ )
+ } bind Repository::class
+
+ single {
+ RealSongRepository(get())
+ } bind SongRepository::class
+
+ single {
+ RealGenreRepository(get(), get())
+ } bind GenreRepository::class
+
+ single {
+ RealAlbumRepository(get())
+ } bind AlbumRepository::class
+
+ single {
+ RealArtistRepository(get(), get())
+ } bind ArtistRepository::class
+
+ single {
+ RealPlaylistRepository(get())
+ } bind PlaylistRepository::class
+
+ single {
+ RealTopPlayedRepository(get(), get(), get(), get())
+ } bind TopPlayedRepository::class
+
+ single {
+ RealLastAddedRepository(
+ get(),
+ get(),
+ get()
+ )
+ } bind LastAddedRepository::class
+
+ single {
+ RealSearchRepository(
+ get(),
+ get(),
+ get(),
+ get(),
+ get()
+ )
+ }
+}
+
+private val viewModules = module {
+
+ viewModel {
+ LibraryViewModel(get())
+ }
+
+ viewModel { (albumId: Long) ->
+ AlbumDetailsViewModel(
+ get(),
+ albumId
+ )
+ }
+
+ viewModel { (artistId: Long?, artistName: String?) ->
+ ArtistDetailsViewModel(
+ get(),
+ artistId,
+ artistName
+ )
+ }
+
+ viewModel { (playlistId: Long) ->
+ PlaylistDetailsViewModel(
+ get(),
+ playlistId
+ )
+ }
+
+ viewModel { (genre: Genre) ->
+ GenreDetailsViewModel(
+ get(),
+ genre
+ )
+ }
+}
+
+val appModules = listOf(mainModule, dataModule, autoModule, viewModules, roomModule)
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/RetroApplication.java b/app/src/main/java/code/name/monkey/retromusic/RetroApplication.java
deleted file mode 100644
index cc2abeef8..000000000
--- a/app/src/main/java/code/name/monkey/retromusic/RetroApplication.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package code.name.monkey.retromusic;
-
-import android.os.Build;
-import android.support.annotation.NonNull;
-import android.support.multidex.MultiDexApplication;
-import code.name.monkey.appthemehelper.ThemeStore;
-import code.name.monkey.retromusic.appshortcuts.DynamicShortcutManager;
-import com.anjlab.android.iab.v3.BillingProcessor;
-import com.anjlab.android.iab.v3.TransactionDetails;
-import uk.co.chrisjenx.calligraphy.CalligraphyConfig;
-
-public class RetroApplication extends MultiDexApplication {
-
- public static final String PRO_VERSION_PRODUCT_ID = "pro_version";
-
- private static RetroApplication app;
-
- private BillingProcessor billingProcessor;
-
- public static RetroApplication getInstance() {
- return app;
- }
-
- public static boolean isProVersion() {
- return BuildConfig.DEBUG || app.billingProcessor.isPurchased(PRO_VERSION_PRODUCT_ID);
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- app = this;
-
- // default theme
- if (!ThemeStore.isConfigured(this, 1)) {
- ThemeStore.editTheme(this)
- .accentColorRes(R.color.md_green_A200)
- .commit();
- }
-
- CalligraphyConfig.initDefault(new CalligraphyConfig.Builder()
- .setDefaultFontPath("fonts/circular_std_book.otf")
- .setFontAttrId(R.attr.fontPath)
- .build()
- );
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
- new DynamicShortcutManager(this).initDynamicShortcuts();
- }
-
- // automatically restores purchases
- billingProcessor = new BillingProcessor(this, BuildConfig.GOOGLE_PLAY_LICENSE_KEY,
- new BillingProcessor.IBillingHandler() {
- @Override
- public void onProductPurchased(@NonNull String productId, TransactionDetails details) {
- }
-
- @Override
- public void onPurchaseHistoryRestored() {
- //Toast.makeText(App.this, R.string.restored_previous_purchase_please_restart, Toast.LENGTH_LONG).show();
- }
-
- @Override
- public void onBillingError(int errorCode, Throwable error) {
- }
-
- @Override
- public void onBillingInitialized() {
- }
- });
- }
-
- @Override
- public void onTerminate() {
- super.onTerminate();
- billingProcessor.release();
- }
-}
diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/DriveModeActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/DriveModeActivity.kt
new file mode 100644
index 000000000..af3890d22
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/activities/DriveModeActivity.kt
@@ -0,0 +1,258 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.activities
+
+import android.content.Intent
+import android.graphics.Color
+import android.graphics.PorterDuff
+import android.os.Bundle
+import androidx.lifecycle.lifecycleScope
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
+import code.name.monkey.retromusic.databinding.ActivityDriveModeBinding
+import code.name.monkey.retromusic.db.toSongEntity
+import code.name.monkey.retromusic.extensions.accentColor
+import code.name.monkey.retromusic.extensions.drawAboveSystemBars
+import code.name.monkey.retromusic.glide.BlurTransformation
+import code.name.monkey.retromusic.glide.RetroGlideExtension
+import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
+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.model.Song
+import code.name.monkey.retromusic.repository.RealRepository
+import code.name.monkey.retromusic.service.MusicService
+import code.name.monkey.retromusic.util.MusicUtil
+import com.bumptech.glide.Glide
+import com.google.android.material.slider.Slider
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import org.koin.android.ext.android.inject
+
+
+/**
+ * Created by hemanths on 2020-02-02.
+ */
+
+class DriveModeActivity : AbsMusicServiceActivity(), Callback {
+
+ private lateinit var binding: ActivityDriveModeBinding
+ private var lastPlaybackControlsColor: Int = Color.GRAY
+ private var lastDisabledPlaybackControlsColor: Int = Color.GRAY
+ private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper
+ private val repository: RealRepository by inject()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityDriveModeBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ setUpMusicControllers()
+
+ progressViewUpdateHelper = MusicProgressViewUpdateHelper(this)
+ lastPlaybackControlsColor = accentColor()
+ binding.close.setOnClickListener {
+ onBackPressedDispatcher.onBackPressed()
+ }
+ binding.repeatButton.drawAboveSystemBars()
+ }
+
+ private fun setUpMusicControllers() {
+ setUpPlayPauseFab()
+ setUpPrevNext()
+ setUpRepeatButton()
+ setUpShuffleButton()
+ setUpProgressSlider()
+ setupFavouriteToggle()
+ }
+
+ private fun setupFavouriteToggle() {
+ binding.songFavourite.setOnClickListener {
+ toggleFavorite(MusicPlayerRemote.currentSong)
+ }
+ }
+
+ private fun toggleFavorite(song: Song) {
+ lifecycleScope.launch(Dispatchers.IO) {
+ val playlist = repository.favoritePlaylist()
+ val songEntity = song.toSongEntity(playlist.playListId)
+ val isFavorite = repository.isSongFavorite(song.id)
+ if (isFavorite) {
+ repository.removeSongFromPlaylist(songEntity)
+ } else {
+ repository.insertSongs(listOf(song.toSongEntity(playlist.playListId)))
+ }
+ sendBroadcast(Intent(MusicService.FAVORITE_STATE_CHANGED))
+ }
+ }
+
+ private fun updateFavorite() {
+ lifecycleScope.launch(Dispatchers.IO) {
+ val isFavorite: Boolean =
+ repository.isSongFavorite(MusicPlayerRemote.currentSong.id)
+ withContext(Dispatchers.Main) {
+ binding.songFavourite.setImageResource(if (isFavorite) R.drawable.ic_favorite else R.drawable.ic_favorite_border)
+ }
+ }
+ }
+
+ private fun setUpProgressSlider() {
+ binding.progressSlider.addOnChangeListener { _: Slider, progress: Float, fromUser: Boolean ->
+ if (fromUser) {
+ MusicPlayerRemote.seekTo(progress.toInt())
+ onUpdateProgressViews(
+ MusicPlayerRemote.songProgressMillis,
+ MusicPlayerRemote.songDurationMillis
+ )
+ }
+ }
+ }
+
+ override fun onPause() {
+ super.onPause()
+ progressViewUpdateHelper.stop()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ progressViewUpdateHelper.start()
+ }
+
+ private fun setUpPrevNext() {
+ binding.nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() }
+ binding.previousButton.setOnClickListener { MusicPlayerRemote.back() }
+ }
+
+ private fun setUpShuffleButton() {
+ binding.shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() }
+ }
+
+ private fun setUpRepeatButton() {
+ binding.repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() }
+ }
+
+ private fun setUpPlayPauseFab() {
+ binding.playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler())
+ }
+
+ override fun onRepeatModeChanged() {
+ super.onRepeatModeChanged()
+ updateRepeatState()
+ }
+
+ override fun onShuffleModeChanged() {
+ super.onShuffleModeChanged()
+ updateShuffleState()
+ }
+
+ override fun onPlayStateChanged() {
+ super.onPlayStateChanged()
+ updatePlayPauseDrawableState()
+ }
+
+ override fun onServiceConnected() {
+ super.onServiceConnected()
+ updatePlayPauseDrawableState()
+ updateSong()
+ updateRepeatState()
+ updateShuffleState()
+ updateFavorite()
+ }
+
+ private fun updatePlayPauseDrawableState() {
+ if (MusicPlayerRemote.isPlaying) {
+ binding.playPauseButton.setImageResource(R.drawable.ic_pause)
+ } else {
+ binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow)
+ }
+ }
+
+ fun updateShuffleState() {
+ when (MusicPlayerRemote.shuffleMode) {
+ MusicService.SHUFFLE_MODE_SHUFFLE -> binding.shuffleButton.setColorFilter(
+ lastPlaybackControlsColor,
+ PorterDuff.Mode.SRC_IN
+ )
+
+ else -> binding.shuffleButton.setColorFilter(
+ lastDisabledPlaybackControlsColor,
+ PorterDuff.Mode.SRC_IN
+ )
+ }
+ }
+
+ private fun updateRepeatState() {
+ when (MusicPlayerRemote.repeatMode) {
+ MusicService.REPEAT_MODE_NONE -> {
+ binding.repeatButton.setImageResource(R.drawable.ic_repeat)
+ binding.repeatButton.setColorFilter(
+ lastDisabledPlaybackControlsColor,
+ PorterDuff.Mode.SRC_IN
+ )
+ }
+
+ MusicService.REPEAT_MODE_ALL -> {
+ binding.repeatButton.setImageResource(R.drawable.ic_repeat)
+ binding.repeatButton.setColorFilter(
+ lastPlaybackControlsColor,
+ PorterDuff.Mode.SRC_IN
+ )
+ }
+
+ MusicService.REPEAT_MODE_THIS -> {
+ binding.repeatButton.setImageResource(R.drawable.ic_repeat_one)
+ binding.repeatButton.setColorFilter(
+ lastPlaybackControlsColor,
+ PorterDuff.Mode.SRC_IN
+ )
+ }
+ }
+ }
+
+ override fun onPlayingMetaChanged() {
+ super.onPlayingMetaChanged()
+ updateSong()
+ updateFavorite()
+ }
+
+ override fun onFavoriteStateChanged() {
+ super.onFavoriteStateChanged()
+ updateFavorite()
+ }
+
+ private fun updateSong() {
+ val song = MusicPlayerRemote.currentSong
+
+ binding.songTitle.text = song.title
+ binding.songText.text = song.artistName
+
+ Glide.with(this)
+ .load(RetroGlideExtension.getSongModel(song))
+ .songCoverOptions(song)
+ .transform(BlurTransformation.Builder(this).build())
+ .into(binding.image)
+ }
+
+ override fun onUpdateProgressViews(progress: Int, total: Int) {
+ binding.progressSlider.run {
+ valueTo = total.toFloat()
+ value = progress.toFloat().coerceIn(valueFrom, valueTo)
+ }
+
+ binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
+ binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/ErrorActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/ErrorActivity.kt
new file mode 100644
index 000000000..6823b80f8
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/activities/ErrorActivity.kt
@@ -0,0 +1,80 @@
+package code.name.monkey.retromusic.activities
+
+import android.os.Bundle
+import android.widget.Button
+import android.widget.ImageView
+import androidx.appcompat.app.AppCompatActivity
+import cat.ereza.customactivityoncrash.CustomActivityOnCrash
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.util.FileUtils.createFile
+import code.name.monkey.retromusic.util.Share.shareFile
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import java.text.DateFormat
+import java.text.SimpleDateFormat
+import java.util.*
+
+class ErrorActivity : AppCompatActivity() {
+ private val dayFormat: DateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
+ private val reportPrefix = "bug_report-"
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(cat.ereza.customactivityoncrash.R.layout.customactivityoncrash_default_error_activity)
+
+ val restartButton =
+ findViewById(cat.ereza.customactivityoncrash.R.id.customactivityoncrash_error_activity_restart_button)
+
+ val config = CustomActivityOnCrash.getConfigFromIntent(intent)
+ if (config == null) {
+ finish()
+ return
+ }
+ restartButton.setText(cat.ereza.customactivityoncrash.R.string.customactivityoncrash_error_activity_restart_app)
+ restartButton.setOnClickListener {
+ CustomActivityOnCrash.restartApplication(
+ this@ErrorActivity,
+ config
+ )
+ }
+ val moreInfoButton =
+ findViewById(cat.ereza.customactivityoncrash.R.id.customactivityoncrash_error_activity_more_info_button)
+
+ moreInfoButton.setOnClickListener { //We retrieve all the error data and show it
+ MaterialAlertDialogBuilder(this@ErrorActivity)
+ .setTitle(cat.ereza.customactivityoncrash.R.string.customactivityoncrash_error_activity_error_details_title)
+ .setMessage(
+ CustomActivityOnCrash.getAllErrorDetailsFromIntent(
+ this@ErrorActivity,
+ intent
+ )
+ )
+ .setPositiveButton(
+ cat.ereza.customactivityoncrash.R.string.customactivityoncrash_error_activity_error_details_close,
+ null
+ )
+ .setNeutralButton(
+ R.string.customactivityoncrash_error_activity_error_details_share
+ ) { _, _ ->
+
+ val bugReport = createFile(
+ context = this,
+ "Bug Report",
+ "$reportPrefix${dayFormat.format(Date())}",
+ CustomActivityOnCrash.getAllErrorDetailsFromIntent(
+ this@ErrorActivity,
+ intent
+ ), ".txt"
+ )
+ shareFile(this, bugReport, "text/*")
+ }
+ .show()
+ }
+ val errorActivityDrawableId = config.errorDrawable
+ val errorImageView =
+ findViewById(cat.ereza.customactivityoncrash.R.id.customactivityoncrash_error_activity_image)
+ if (errorActivityDrawableId != null) {
+ errorImageView.setImageResource(
+ errorActivityDrawableId
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/LicenseActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/LicenseActivity.kt
new file mode 100644
index 000000000..e41352dae
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/activities/LicenseActivity.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2019 Hemanth Savarala.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by
+ * the Free Software Foundation either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ */
+package code.name.monkey.retromusic.activities
+
+import android.graphics.Color
+import android.os.Bundle
+import android.view.MenuItem
+import code.name.monkey.appthemehelper.util.ATHUtil.isWindowBackgroundDark
+import code.name.monkey.appthemehelper.util.ColorUtil.lightenColor
+import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
+import code.name.monkey.retromusic.activities.base.AbsThemeActivity
+import code.name.monkey.retromusic.databinding.ActivityLicenseBinding
+import code.name.monkey.retromusic.extensions.accentColor
+import code.name.monkey.retromusic.extensions.drawAboveSystemBars
+import code.name.monkey.retromusic.extensions.surfaceColor
+import java.io.BufferedReader
+import java.io.InputStreamReader
+import java.nio.charset.StandardCharsets
+
+/** Created by hemanths on 2019-09-27. */
+class LicenseActivity : AbsThemeActivity() {
+ private lateinit var binding: ActivityLicenseBinding
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityLicenseBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ setSupportActionBar(binding.toolbar)
+ ToolbarContentTintHelper.colorBackButton(binding.toolbar)
+ try {
+ val buf = StringBuilder()
+ val json = assets.open("license.html")
+ BufferedReader(InputStreamReader(json, StandardCharsets.UTF_8)).use { br ->
+ var str: String?
+ while (br.readLine().also { str = it } != null) {
+ buf.append(str)
+ }
+ }
+
+ // Inject color values for WebView body background and links
+ val isDark = isWindowBackgroundDark(this)
+ val backgroundColor = colorToCSS(
+ surfaceColor(Color.parseColor(if (isDark) "#424242" else "#ffffff"))
+ )
+ val contentColor = colorToCSS(Color.parseColor(if (isDark) "#ffffff" else "#000000"))
+ val changeLog = buf.toString()
+ .replace(
+ "{style-placeholder}", String.format(
+ "body { background-color: %s; color: %s; }", backgroundColor, contentColor
+ )
+ )
+ .replace("{link-color}", colorToCSS(accentColor()))
+ .replace(
+ "{link-color-active}",
+ colorToCSS(
+ lightenColor(accentColor())
+ )
+ )
+ binding.license.loadData(changeLog, "text/html", "UTF-8")
+ } catch (e: Throwable) {
+ binding.license.loadData(
+ "Unable to load " + e.localizedMessage + "
", "text/html", "UTF-8"
+ )
+ }
+ binding.license.drawAboveSystemBars()
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ if (item.itemId == android.R.id.home) {
+ onBackPressedDispatcher.onBackPressed()
+ return true
+ }
+ return super.onOptionsItemSelected(item)
+ }
+
+ private fun colorToCSS(color: Int): String {
+ return String.format(
+ "rgb(%d, %d, %d)",
+ Color.red(color),
+ Color.green(color),
+ Color.blue(color)
+ ) // on API 29, WebView doesn't load with hex colors
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/LockScreenActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/LockScreenActivity.kt
new file mode 100644
index 000000000..3ac219871
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/activities/LockScreenActivity.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.activities
+
+import android.app.KeyguardManager
+import android.os.Bundle
+import android.view.WindowManager
+import androidx.core.content.getSystemService
+import code.name.monkey.appthemehelper.util.VersionUtils
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
+import code.name.monkey.retromusic.databinding.ActivityLockScreenBinding
+import code.name.monkey.retromusic.extensions.hideStatusBar
+import code.name.monkey.retromusic.extensions.setTaskDescriptionColorAuto
+import code.name.monkey.retromusic.extensions.whichFragment
+import code.name.monkey.retromusic.fragments.player.lockscreen.LockScreenControlsFragment
+import code.name.monkey.retromusic.glide.RetroGlideExtension
+import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
+import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
+import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
+import code.name.monkey.retromusic.helper.MusicPlayerRemote
+import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
+import com.bumptech.glide.Glide
+import com.r0adkll.slidr.Slidr
+import com.r0adkll.slidr.model.SlidrConfig
+import com.r0adkll.slidr.model.SlidrListener
+import com.r0adkll.slidr.model.SlidrPosition
+
+class LockScreenActivity : AbsMusicServiceActivity() {
+ private lateinit var binding: ActivityLockScreenBinding
+ private var fragment: LockScreenControlsFragment? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ lockScreenInit()
+ binding = ActivityLockScreenBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ hideStatusBar()
+ setTaskDescriptionColorAuto()
+
+ val config = SlidrConfig.Builder().listener(object : SlidrListener {
+ override fun onSlideStateChanged(state: Int) {
+ }
+
+ override fun onSlideChange(percent: Float) {
+ }
+
+ override fun onSlideOpened() {
+ }
+
+ override fun onSlideClosed(): Boolean {
+ if (VersionUtils.hasOreo()) {
+ val keyguardManager =
+ getSystemService()
+ keyguardManager?.requestDismissKeyguard(this@LockScreenActivity, null)
+ }
+ finish()
+ return true
+ }
+ }).position(SlidrPosition.BOTTOM).build()
+
+ Slidr.attach(this, config)
+
+ fragment = whichFragment(R.id.playback_controls_fragment)
+
+ binding.slide.apply {
+ translationY = 100f
+ alpha = 0f
+ animate().translationY(0f).alpha(1f).setDuration(1500).start()
+ }
+ }
+
+ @Suppress("Deprecation")
+ private fun lockScreenInit() {
+ if (VersionUtils.hasOreoMR1()) {
+ setShowWhenLocked(true)
+ //setTurnScreenOn(true)
+ } else {
+ window.addFlags(
+ WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ // or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+ )
+ }
+ }
+
+ override fun onPlayingMetaChanged() {
+ super.onPlayingMetaChanged()
+ updateSongs()
+ }
+
+ override fun onServiceConnected() {
+ super.onServiceConnected()
+ updateSongs()
+ }
+
+ private fun updateSongs() {
+ val song = MusicPlayerRemote.currentSong
+ Glide.with(this)
+ .asBitmapPalette()
+ .songCoverOptions(song)
+ .load(RetroGlideExtension.getSongModel(song))
+ .dontAnimate()
+ .into(object : RetroMusicColoredTarget(binding.image) {
+ override fun onColorReady(colors: MediaNotificationProcessor) {
+ fragment?.setColor(colors)
+ }
+ })
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/MainActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/MainActivity.kt
new file mode 100644
index 000000000..e9cb29abe
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/activities/MainActivity.kt
@@ -0,0 +1,214 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.activities
+
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.provider.MediaStore
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.contains
+import androidx.navigation.ui.setupWithNavController
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity
+import code.name.monkey.retromusic.extensions.*
+import code.name.monkey.retromusic.helper.MusicPlayerRemote
+import code.name.monkey.retromusic.helper.SearchQueryHelper.getSongs
+import code.name.monkey.retromusic.interfaces.IScrollHelper
+import code.name.monkey.retromusic.model.CategoryInfo
+import code.name.monkey.retromusic.model.Song
+import code.name.monkey.retromusic.repository.PlaylistSongsLoader
+import code.name.monkey.retromusic.service.MusicService
+import code.name.monkey.retromusic.util.PreferenceUtil
+import code.name.monkey.retromusic.util.logE
+import kotlinx.coroutines.Dispatchers.IO
+import kotlinx.coroutines.launch
+import org.koin.android.ext.android.get
+
+class MainActivity : AbsSlidingMusicPanelActivity() {
+ companion object {
+ const val TAG = "MainActivity"
+ const val EXPAND_PANEL = "expand_panel"
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setTaskDescriptionColorAuto()
+ hideStatusBar()
+ updateTabs()
+
+ setupNavigationController()
+
+ WhatsNewFragment.showChangeLog(this)
+ }
+
+ private fun setupNavigationController() {
+ val navController = findNavController(R.id.fragment_container)
+ val navInflater = navController.navInflater
+ val navGraph = navInflater.inflate(R.navigation.main_graph)
+
+ val categoryInfo: CategoryInfo = PreferenceUtil.libraryCategory.first { it.visible }
+ if (categoryInfo.visible) {
+ if (!navGraph.contains(PreferenceUtil.lastTab)) PreferenceUtil.lastTab =
+ categoryInfo.category.id
+ navGraph.setStartDestination(
+ if (PreferenceUtil.rememberLastTab) {
+ PreferenceUtil.lastTab.let {
+ if (it == 0) {
+ categoryInfo.category.id
+ } else {
+ it
+ }
+ }
+ } else categoryInfo.category.id
+ )
+ }
+ navController.graph = navGraph
+ navigationView.setupWithNavController(navController)
+ // Scroll Fragment to top
+ navigationView.setOnItemReselectedListener {
+ currentFragment(R.id.fragment_container).apply {
+ if (this is IScrollHelper) {
+ scrollToTop()
+ }
+ }
+ }
+ navController.addOnDestinationChangedListener { _, destination, _ ->
+ if (destination.id == navGraph.startDestinationId) {
+ currentFragment(R.id.fragment_container)?.enterTransition = null
+ }
+ when (destination.id) {
+ R.id.action_home, R.id.action_song, R.id.action_album, R.id.action_artist, R.id.action_folder, R.id.action_playlist, R.id.action_genre, R.id.action_search -> {
+ // Save the last tab
+ if (PreferenceUtil.rememberLastTab) {
+ saveTab(destination.id)
+ }
+ // Show Bottom Navigation Bar
+ setBottomNavVisibility(visible = true, animate = true)
+ }
+ R.id.playing_queue_fragment -> {
+ setBottomNavVisibility(visible = false, hideBottomSheet = true)
+ }
+ else -> setBottomNavVisibility(
+ visible = false,
+ animate = true
+ ) // Hide Bottom Navigation Bar
+ }
+ }
+ }
+
+ private fun saveTab(id: Int) {
+ if (PreferenceUtil.libraryCategory.firstOrNull { it.category.id == id }?.visible == true) {
+ PreferenceUtil.lastTab = id
+ }
+ }
+
+ override fun onSupportNavigateUp(): Boolean =
+ findNavController(R.id.fragment_container).navigateUp()
+
+ override fun onNewIntent(intent: Intent?) {
+ super.onNewIntent(intent)
+ val expand = intent?.extra(EXPAND_PANEL)?.value ?: false
+ if (expand && PreferenceUtil.isExpandPanel) {
+ fromNotification = true
+ slidingPanel.bringToFront()
+ expandPanel()
+ intent?.removeExtra(EXPAND_PANEL)
+ }
+ }
+
+ override fun onServiceConnected() {
+ super.onServiceConnected()
+ intent ?: return
+ handlePlaybackIntent(intent)
+ }
+
+ private fun handlePlaybackIntent(intent: Intent) {
+ lifecycleScope.launch(IO) {
+ val uri: Uri? = intent.data
+ val mimeType: String? = intent.type
+ var handled = false
+ if (intent.action != null &&
+ intent.action == MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH
+ ) {
+ val songs: List = getSongs(intent.extras!!)
+ if (MusicPlayerRemote.shuffleMode == MusicService.SHUFFLE_MODE_SHUFFLE) {
+ MusicPlayerRemote.openAndShuffleQueue(songs, true)
+ } else {
+ MusicPlayerRemote.openQueue(songs, 0, true)
+ }
+ handled = true
+ }
+ if (uri != null && uri.toString().isNotEmpty()) {
+ MusicPlayerRemote.playFromUri(this@MainActivity, uri)
+ handled = true
+ } else if (MediaStore.Audio.Playlists.CONTENT_TYPE == mimeType) {
+ val id = parseLongFromIntent(intent, "playlistId", "playlist")
+ if (id >= 0L) {
+ val position: Int = intent.getIntExtra("position", 0)
+ val songs: List = PlaylistSongsLoader.getPlaylistSongList(get(), id)
+ MusicPlayerRemote.openQueue(songs, position, true)
+ handled = true
+ }
+ } else if (MediaStore.Audio.Albums.CONTENT_TYPE == mimeType) {
+ val id = parseLongFromIntent(intent, "albumId", "album")
+ if (id >= 0L) {
+ val position: Int = intent.getIntExtra("position", 0)
+ val songs = libraryViewModel.albumById(id).songs
+ MusicPlayerRemote.openQueue(
+ songs,
+ position,
+ true
+ )
+ handled = true
+ }
+ } else if (MediaStore.Audio.Artists.CONTENT_TYPE == mimeType) {
+ val id = parseLongFromIntent(intent, "artistId", "artist")
+ if (id >= 0L) {
+ val position: Int = intent.getIntExtra("position", 0)
+ val songs: List = libraryViewModel.artistById(id).songs
+ MusicPlayerRemote.openQueue(
+ songs,
+ position,
+ true
+ )
+ handled = true
+ }
+ }
+ if (handled) {
+ setIntent(Intent())
+ }
+ }
+ }
+
+ private fun parseLongFromIntent(
+ intent: Intent,
+ longKey: String,
+ stringKey: String,
+ ): Long {
+ var id = intent.getLongExtra(longKey, -1)
+ if (id < 0) {
+ val idString = intent.getStringExtra(stringKey)
+ if (idString != null) {
+ try {
+ id = idString.toLong()
+ } catch (e: NumberFormatException) {
+ logE(e)
+ }
+ }
+ }
+ return id
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/PermissionActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/PermissionActivity.kt
new file mode 100644
index 000000000..5cb64cf93
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/activities/PermissionActivity.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.activities
+
+import android.Manifest.permission.BLUETOOTH_CONNECT
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.res.ColorStateList
+import android.os.Build
+import android.os.Bundle
+import android.provider.Settings
+import androidx.activity.OnBackPressedCallback
+import androidx.annotation.RequiresApi
+import androidx.core.app.ActivityCompat
+import androidx.core.net.toUri
+import androidx.core.text.parseAsHtml
+import androidx.core.view.isVisible
+import code.name.monkey.appthemehelper.util.VersionUtils
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
+import code.name.monkey.retromusic.databinding.ActivityPermissionBinding
+import code.name.monkey.retromusic.extensions.*
+
+class PermissionActivity : AbsMusicServiceActivity() {
+ private lateinit var binding: ActivityPermissionBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityPermissionBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ setStatusBarColorAuto()
+ setTaskDescriptionColorAuto()
+ setupTitle()
+
+ binding.storagePermission.setButtonClick {
+ requestPermissions()
+ }
+ if (VersionUtils.hasMarshmallow()) {
+ binding.audioPermission.show()
+ binding.audioPermission.setButtonClick {
+ if (!hasAudioPermission()) {
+ val intent = Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS)
+ intent.data = ("package:" + applicationContext.packageName).toUri()
+ startActivity(intent)
+ }
+ }
+ }
+
+ if (VersionUtils.hasS()) {
+ binding.bluetoothPermission.show()
+ binding.bluetoothPermission.setButtonClick {
+ ActivityCompat.requestPermissions(
+ this,
+ arrayOf(BLUETOOTH_CONNECT),
+ BLUETOOTH_PERMISSION_REQUEST
+ )
+ }
+ } else {
+ binding.audioPermission.setNumber("2")
+ }
+
+ binding.finish.accentBackgroundColor()
+ binding.finish.setOnClickListener {
+ if (hasPermissions()) {
+ startActivity(
+ Intent(this, MainActivity::class.java).addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK or
+ Intent.FLAG_ACTIVITY_CLEAR_TASK
+ )
+ )
+ finish()
+ }
+ }
+ onBackPressedDispatcher.addCallback(object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ finishAffinity()
+ remove()
+ }
+ })
+ }
+
+ private fun setupTitle() {
+ val appName =
+ getString(
+ R.string.message_welcome,
+ "Metro "
+ )
+ .parseAsHtml()
+ binding.appNameText.text = appName
+ }
+
+ override fun onResume() {
+ super.onResume()
+ binding.finish.isEnabled = hasStoragePermission()
+ if (hasStoragePermission()) {
+ binding.storagePermission.checkImage.isVisible = true
+ binding.storagePermission.checkImage.imageTintList =
+ ColorStateList.valueOf(accentColor())
+ }
+ if (VersionUtils.hasMarshmallow()) {
+ if (hasAudioPermission()) {
+ binding.audioPermission.checkImage.isVisible = true
+ binding.audioPermission.checkImage.imageTintList =
+ ColorStateList.valueOf(accentColor())
+ }
+ }
+ if (VersionUtils.hasS()) {
+ if (hasBluetoothPermission()) {
+ binding.bluetoothPermission.checkImage.isVisible = true
+ binding.bluetoothPermission.checkImage.imageTintList =
+ ColorStateList.valueOf(accentColor())
+ }
+ }
+ }
+
+ private fun hasStoragePermission(): Boolean {
+ return hasPermissions()
+ }
+
+ @RequiresApi(Build.VERSION_CODES.S)
+ private fun hasBluetoothPermission(): Boolean {
+ return ActivityCompat.checkSelfPermission(
+ this,
+ BLUETOOTH_CONNECT
+ ) == PackageManager.PERMISSION_GRANTED
+ }
+
+ @RequiresApi(Build.VERSION_CODES.M)
+ private fun hasAudioPermission(): Boolean {
+ return Settings.System.canWrite(this)
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/ShareInstagramStory.kt b/app/src/main/java/code/name/monkey/retromusic/activities/ShareInstagramStory.kt
new file mode 100644
index 000000000..546c9d794
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/activities/ShareInstagramStory.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.activities
+
+import android.content.res.ColorStateList
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.drawable.GradientDrawable
+import android.os.Bundle
+import android.provider.MediaStore.Images.Media
+import android.view.MenuItem
+import androidx.core.net.toUri
+import androidx.core.os.BundleCompat
+import androidx.core.view.drawToBitmap
+import code.name.monkey.appthemehelper.util.ColorUtil
+import code.name.monkey.appthemehelper.util.MaterialValueHelper
+import code.name.monkey.retromusic.activities.base.AbsThemeActivity
+import code.name.monkey.retromusic.databinding.ActivityShareInstagramBinding
+import code.name.monkey.retromusic.extensions.accentColor
+import code.name.monkey.retromusic.extensions.setStatusBarColor
+import code.name.monkey.retromusic.glide.RetroGlideExtension
+import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
+import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
+import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
+import code.name.monkey.retromusic.model.Song
+import code.name.monkey.retromusic.util.Share
+import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
+import com.bumptech.glide.Glide
+
+/**
+ * Created by hemanths on 2020-02-02.
+ */
+
+class ShareInstagramStory : AbsThemeActivity() {
+
+ private lateinit var binding: ActivityShareInstagramBinding
+
+ companion object {
+ const val EXTRA_SONG = "extra_song"
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ if (item.itemId == android.R.id.home) {
+ onBackPressedDispatcher.onBackPressed()
+ return true
+ }
+ return super.onOptionsItemSelected(item)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityShareInstagramBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ setStatusBarColor(Color.TRANSPARENT)
+
+ binding.toolbar.setBackgroundColor(Color.TRANSPARENT)
+ setSupportActionBar(binding.toolbar)
+
+ val song = intent.extras?.let { BundleCompat.getParcelable(it, EXTRA_SONG, Song::class.java) }
+ song?.let { songFinal ->
+ Glide.with(this)
+ .asBitmapPalette()
+ .songCoverOptions(songFinal)
+ .load(RetroGlideExtension.getSongModel(songFinal))
+ .into(object : RetroMusicColoredTarget(binding.image) {
+ override fun onColorReady(colors: MediaNotificationProcessor) {
+ setColors(colors.backgroundColor)
+ }
+ })
+
+ binding.shareTitle.text = songFinal.title
+ binding.shareText.text = songFinal.artistName
+ binding.shareButton.setOnClickListener {
+ val path: String = Media.insertImage(
+ contentResolver,
+ binding.mainContent.drawToBitmap(Bitmap.Config.ARGB_8888),
+ "Design", null
+ )
+ Share.shareStoryToSocial(
+ this@ShareInstagramStory,
+ path.toUri()
+ )
+ }
+ }
+ binding.shareButton.setTextColor(
+ MaterialValueHelper.getPrimaryTextColor(
+ this,
+ ColorUtil.isColorLight(accentColor())
+ )
+ )
+ binding.shareButton.backgroundTintList =
+ ColorStateList.valueOf(accentColor())
+ }
+
+ private fun setColors(color: Int) {
+ binding.mainContent.background =
+ GradientDrawable(
+ GradientDrawable.Orientation.TOP_BOTTOM,
+ intArrayOf(color, Color.BLACK)
+ )
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/WhatsNewFragment.kt b/app/src/main/java/code/name/monkey/retromusic/activities/WhatsNewFragment.kt
new file mode 100644
index 000000000..48b39ed35
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/activities/WhatsNewFragment.kt
@@ -0,0 +1,153 @@
+package code.name.monkey.retromusic.activities
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.Color
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.webkit.WebResourceRequest
+import android.webkit.WebView
+import android.webkit.WebViewClient
+import androidx.core.content.pm.PackageInfoCompat
+import androidx.core.widget.NestedScrollView
+import androidx.fragment.app.FragmentActivity
+import code.name.monkey.appthemehelper.util.ATHUtil.isWindowBackgroundDark
+import code.name.monkey.appthemehelper.util.ColorUtil.isColorLight
+import code.name.monkey.appthemehelper.util.ColorUtil.lightenColor
+import code.name.monkey.appthemehelper.util.MaterialValueHelper.getPrimaryTextColor
+import code.name.monkey.retromusic.BuildConfig
+import code.name.monkey.retromusic.Constants
+import code.name.monkey.retromusic.databinding.FragmentWhatsNewBinding
+import code.name.monkey.retromusic.extensions.accentColor
+import code.name.monkey.retromusic.extensions.openUrl
+import code.name.monkey.retromusic.util.PreferenceUtil.lastVersion
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+import java.nio.charset.StandardCharsets
+import java.util.*
+
+class WhatsNewFragment : BottomSheetDialogFragment() {
+ private var _binding: FragmentWhatsNewBinding? = null
+ val binding get() = _binding!!
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentWhatsNewBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ try {
+ val buf = StringBuilder()
+ val stream = requireContext().assets.open("retro-changelog.html")
+ stream.reader(StandardCharsets.UTF_8).buffered().use { br ->
+ var str: String?
+ while (br.readLine().also { str = it } != null) {
+ buf.append(str)
+ }
+ }
+
+ // Inject color values for WebView body background and links
+ val isDark = isWindowBackgroundDark(requireContext())
+ val accentColor = accentColor()
+ binding.webView.setBackgroundColor(0)
+ val contentColor = colorToCSS(Color.parseColor(if (isDark) "#ffffff" else "#000000"))
+ val textColor = colorToCSS(Color.parseColor(if (isDark) "#60FFFFFF" else "#80000000"))
+ val accentColorString = colorToCSS(accentColor())
+ val cardBackgroundColor =
+ colorToCSS(Color.parseColor(if (isDark) "#353535" else "#ffffff"))
+ val accentTextColor = colorToCSS(
+ getPrimaryTextColor(
+ requireContext(), isColorLight(accentColor)
+ )
+ )
+ val changeLog = buf.toString()
+ .replace(
+ "{style-placeholder}",
+ "body { color: $contentColor; } li {color: $textColor;} h3 {color: $accentColorString;} .tag {background-color: $accentColorString; color: $accentTextColor; } div{background-color: $cardBackgroundColor;}"
+ )
+ .replace("{link-color}", colorToCSS(accentColor()))
+ .replace(
+ "{link-color-active}",
+ colorToCSS(
+ lightenColor(accentColor())
+ )
+ )
+ binding.webView.loadData(changeLog, "text/html", "UTF-8")
+ binding.webView.webViewClient = object : WebViewClient() {
+ override fun shouldOverrideUrlLoading(
+ view: WebView?,
+ request: WebResourceRequest?
+ ): Boolean {
+ val url = request?.url ?: return false
+ //you can do checks here e.g. url.host equals to target one
+ startActivity(Intent(Intent.ACTION_VIEW, url))
+ return true
+ }
+ }
+ } catch (e: Throwable) {
+ binding.webView.loadData(
+ "Unable to load " + e.localizedMessage + "
", "text/html", "UTF-8"
+ )
+ }
+ setChangelogRead(requireContext())
+ binding.tgFab.setOnClickListener {
+ openUrl(Constants.TELEGRAM_CHANGE_LOG)
+ }
+ binding.tgFab.accentColor()
+ binding.tgFab.shrink()
+ binding.container.setOnScrollChangeListener { _: NestedScrollView?, _: Int, scrollY: Int, _: Int, oldScrollY: Int ->
+ val dy = scrollY - oldScrollY
+ if (dy > 0) {
+ binding.tgFab.shrink()
+ } else if (dy < 0) {
+ binding.tgFab.extend()
+ }
+ }
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ _binding = null
+ }
+
+ companion object {
+
+ const val TAG = "WhatsNewFragment"
+ private fun colorToCSS(color: Int): String {
+ return String.format(
+ Locale.getDefault(),
+ "rgba(%d, %d, %d, %d)",
+ Color.red(color),
+ Color.green(color),
+ Color.blue(color),
+ Color.alpha(color)
+ ) // on API 29, WebView doesn't load with hex colors
+ }
+
+ private fun setChangelogRead(context: Context) {
+ try {
+ val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
+ val currentVersion = PackageInfoCompat.getLongVersionCode(pInfo)
+ lastVersion = currentVersion
+ } catch (e: PackageManager.NameNotFoundException) {
+ e.printStackTrace()
+ }
+ }
+
+ fun showChangeLog(activity: FragmentActivity) {
+ val pInfo = activity.packageManager.getPackageInfo(activity.packageName, 0)
+ val currentVersion = PackageInfoCompat.getLongVersionCode(pInfo)
+ if (currentVersion > lastVersion && !BuildConfig.DEBUG) {
+ val changelogBottomSheet = WhatsNewFragment()
+ changelogBottomSheet.show(activity.supportFragmentManager, TAG)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsBaseActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsBaseActivity.kt
new file mode 100644
index 000000000..1e0c110d5
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsBaseActivity.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.activities.base
+
+import android.Manifest
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.Rect
+import android.media.AudioManager
+import android.net.Uri
+import android.os.Bundle
+import android.provider.Settings
+import android.view.KeyEvent
+import android.view.MotionEvent
+import android.view.View
+import android.view.inputmethod.InputMethodManager
+import android.widget.EditText
+import androidx.core.app.ActivityCompat
+import androidx.core.content.getSystemService
+import code.name.monkey.appthemehelper.util.VersionUtils
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.extensions.accentColor
+import code.name.monkey.retromusic.extensions.rootView
+import code.name.monkey.retromusic.util.logD
+import com.google.android.material.snackbar.Snackbar
+
+abstract class AbsBaseActivity : AbsThemeActivity() {
+ private var hadPermissions: Boolean = false
+ private lateinit var permissions: Array
+ private var permissionDeniedMessage: String? = null
+
+ open fun getPermissionsToRequest(): Array {
+ return arrayOf()
+ }
+
+ protected fun setPermissionDeniedMessage(message: String) {
+ permissionDeniedMessage = message
+ }
+
+ fun getPermissionDeniedMessage(): String {
+ return if (permissionDeniedMessage == null) getString(R.string.permissions_denied) else permissionDeniedMessage!!
+ }
+
+ private val snackBarContainer: View
+ get() = rootView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ volumeControlStream = AudioManager.STREAM_MUSIC
+ permissions = getPermissionsToRequest()
+ hadPermissions = hasPermissions()
+ permissionDeniedMessage = null
+ }
+
+ override fun onResume() {
+ super.onResume()
+ val hasPermissions = hasPermissions()
+ if (hasPermissions != hadPermissions) {
+ hadPermissions = hasPermissions
+ if (VersionUtils.hasMarshmallow()) {
+ onHasPermissionsChanged(hasPermissions)
+ }
+ }
+ }
+
+ protected open fun onHasPermissionsChanged(hasPermissions: Boolean) {
+ // implemented by sub classes
+ logD(hasPermissions)
+ }
+
+ override fun dispatchKeyEvent(event: KeyEvent): Boolean {
+ if (event.keyCode == KeyEvent.KEYCODE_MENU && event.action == KeyEvent.ACTION_UP) {
+ showOverflowMenu()
+ return true
+ }
+ return super.dispatchKeyEvent(event)
+ }
+
+ private fun showOverflowMenu() {
+ }
+
+ protected open fun requestPermissions() {
+ ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST)
+ }
+
+ protected fun hasPermissions(): Boolean {
+ for (permission in permissions) {
+ if (ActivityCompat.checkSelfPermission(this,
+ permission) != PackageManager.PERMISSION_GRANTED
+ ) {
+ return false
+ }
+ }
+ return true
+ }
+
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array,
+ grantResults: IntArray,
+ ) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ if (requestCode == PERMISSION_REQUEST) {
+ for (grantResult in grantResults) {
+ if (grantResult != PackageManager.PERMISSION_GRANTED) {
+ if (ActivityCompat.shouldShowRequestPermissionRationale(
+ this@AbsBaseActivity, Manifest.permission.READ_EXTERNAL_STORAGE,
+ ) || ActivityCompat.shouldShowRequestPermissionRationale(
+ this@AbsBaseActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ )
+ ) {
+ // User has deny from permission dialog
+ Snackbar.make(
+ snackBarContainer,
+ permissionDeniedMessage!!,
+ Snackbar.LENGTH_SHORT
+ )
+ .setAction(R.string.action_grant) { requestPermissions() }
+ .setActionTextColor(accentColor()).show()
+ } else {
+ // User has deny permission and checked never show permission dialog so you can redirect to Application settings page
+ Snackbar.make(
+ snackBarContainer,
+ permissionDeniedMessage!!,
+ Snackbar.LENGTH_INDEFINITE
+ )
+ .setAction(R.string.action_settings) {
+ val intent = Intent()
+ intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
+ val uri = Uri.fromParts(
+ "package",
+ this@AbsBaseActivity.packageName,
+ null
+ )
+ intent.data = uri
+ startActivity(intent)
+ }.setActionTextColor(accentColor()).show()
+ }
+ return
+ }
+ }
+ hadPermissions = true
+ onHasPermissionsChanged(true)
+ } else if (requestCode == BLUETOOTH_PERMISSION_REQUEST) {
+ for (grantResult in grantResults) {
+ if (grantResult != PackageManager.PERMISSION_GRANTED) {
+ if (ActivityCompat.shouldShowRequestPermissionRationale(
+ this@AbsBaseActivity, Manifest.permission.BLUETOOTH_CONNECT
+ )
+ ) {
+ // User has deny from permission dialog
+ Snackbar.make(
+ snackBarContainer,
+ R.string.permission_bluetooth_denied,
+ Snackbar.LENGTH_SHORT
+ )
+ .setAction(R.string.action_grant) {
+ ActivityCompat.requestPermissions(this,
+ arrayOf(Manifest.permission.BLUETOOTH_CONNECT),
+ BLUETOOTH_PERMISSION_REQUEST)
+ }
+ .setActionTextColor(accentColor()).show()
+ }
+ }
+ }
+ }
+ }
+
+ companion object {
+ const val PERMISSION_REQUEST = 100
+ const val BLUETOOTH_PERMISSION_REQUEST = 101
+ }
+
+ // this lets keyboard close when clicked in background
+ override fun dispatchTouchEvent(event: MotionEvent): Boolean {
+ if (event.action == MotionEvent.ACTION_DOWN) {
+ val v = currentFocus
+ if (v is EditText) {
+ val outRect = Rect()
+ v.getGlobalVisibleRect(outRect)
+ if (!outRect.contains(event.rawX.toInt(), event.rawY.toInt())) {
+ v.clearFocus()
+ getSystemService()?.hideSoftInputFromWindow(
+ v.windowToken,
+ 0
+ )
+ }
+ }
+ }
+ return super.dispatchTouchEvent(event)
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsMusicServiceActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsMusicServiceActivity.kt
new file mode 100644
index 000000000..f9f05c919
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsMusicServiceActivity.kt
@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.activities.base
+
+import android.Manifest
+import android.content.*
+import android.os.Bundle
+import android.os.IBinder
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.lifecycleScope
+import code.name.monkey.appthemehelper.util.VersionUtils
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.db.toPlayCount
+import code.name.monkey.retromusic.helper.MusicPlayerRemote
+import code.name.monkey.retromusic.interfaces.IMusicServiceEventListener
+import code.name.monkey.retromusic.repository.RealRepository
+import code.name.monkey.retromusic.service.MusicService.Companion.FAVORITE_STATE_CHANGED
+import code.name.monkey.retromusic.service.MusicService.Companion.MEDIA_STORE_CHANGED
+import code.name.monkey.retromusic.service.MusicService.Companion.META_CHANGED
+import code.name.monkey.retromusic.service.MusicService.Companion.PLAY_STATE_CHANGED
+import code.name.monkey.retromusic.service.MusicService.Companion.QUEUE_CHANGED
+import code.name.monkey.retromusic.service.MusicService.Companion.REPEAT_MODE_CHANGED
+import code.name.monkey.retromusic.service.MusicService.Companion.SHUFFLE_MODE_CHANGED
+import code.name.monkey.retromusic.util.PreferenceUtil
+import code.name.monkey.retromusic.util.logD
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import org.koin.android.ext.android.inject
+import java.lang.ref.WeakReference
+
+abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventListener {
+
+ private val mMusicServiceEventListeners = ArrayList()
+ private val repository: RealRepository by inject()
+ private var serviceToken: MusicPlayerRemote.ServiceToken? = null
+ private var musicStateReceiver: MusicStateReceiver? = null
+ private var receiverRegistered: Boolean = false
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ serviceToken = MusicPlayerRemote.bindToService(this, object : ServiceConnection {
+ override fun onServiceConnected(name: ComponentName, service: IBinder) {
+ this@AbsMusicServiceActivity.onServiceConnected()
+ }
+
+ override fun onServiceDisconnected(name: ComponentName) {
+ this@AbsMusicServiceActivity.onServiceDisconnected()
+ }
+ })
+
+ setPermissionDeniedMessage(getString(R.string.permission_external_storage_denied))
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ MusicPlayerRemote.unbindFromService(serviceToken)
+ if (receiverRegistered) {
+ unregisterReceiver(musicStateReceiver)
+ receiverRegistered = false
+ }
+ }
+
+ fun addMusicServiceEventListener(listenerI: IMusicServiceEventListener?) {
+ if (listenerI != null) {
+ mMusicServiceEventListeners.add(listenerI)
+ }
+ }
+
+ fun removeMusicServiceEventListener(listenerI: IMusicServiceEventListener?) {
+ if (listenerI != null) {
+ mMusicServiceEventListeners.remove(listenerI)
+ }
+ }
+
+ override fun onServiceConnected() {
+ if (!receiverRegistered) {
+ musicStateReceiver = MusicStateReceiver(this)
+
+ val filter = IntentFilter()
+ filter.addAction(PLAY_STATE_CHANGED)
+ filter.addAction(SHUFFLE_MODE_CHANGED)
+ filter.addAction(REPEAT_MODE_CHANGED)
+ filter.addAction(META_CHANGED)
+ filter.addAction(QUEUE_CHANGED)
+ filter.addAction(MEDIA_STORE_CHANGED)
+ filter.addAction(FAVORITE_STATE_CHANGED)
+
+ ContextCompat.registerReceiver(this, musicStateReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED)
+ receiverRegistered = true
+ }
+
+ for (listener in mMusicServiceEventListeners) {
+ listener.onServiceConnected()
+ }
+ }
+
+ override fun onServiceDisconnected() {
+ if (receiverRegistered) {
+ unregisterReceiver(musicStateReceiver)
+ receiverRegistered = false
+ }
+
+ for (listener in mMusicServiceEventListeners) {
+ listener.onServiceDisconnected()
+ }
+ }
+
+ override fun onPlayingMetaChanged() {
+ for (listener in mMusicServiceEventListeners) {
+ listener.onPlayingMetaChanged()
+ }
+ lifecycleScope.launch(Dispatchers.IO) {
+ if (!PreferenceUtil.pauseHistory) {
+ repository.upsertSongInHistory(MusicPlayerRemote.currentSong)
+ }
+ val song = repository.findSongExistInPlayCount(MusicPlayerRemote.currentSong.id)
+ ?.apply { playCount += 1 }
+ ?: MusicPlayerRemote.currentSong.toPlayCount()
+
+ repository.upsertSongInPlayCount(song)
+ }
+ }
+
+ override fun onQueueChanged() {
+ for (listener in mMusicServiceEventListeners) {
+ listener.onQueueChanged()
+ }
+ }
+
+ override fun onPlayStateChanged() {
+ for (listener in mMusicServiceEventListeners) {
+ listener.onPlayStateChanged()
+ }
+ }
+
+ override fun onMediaStoreChanged() {
+ for (listener in mMusicServiceEventListeners) {
+ listener.onMediaStoreChanged()
+ }
+ }
+
+ override fun onRepeatModeChanged() {
+ for (listener in mMusicServiceEventListeners) {
+ listener.onRepeatModeChanged()
+ }
+ }
+
+ override fun onShuffleModeChanged() {
+ for (listener in mMusicServiceEventListeners) {
+ listener.onShuffleModeChanged()
+ }
+ }
+
+ override fun onFavoriteStateChanged() {
+ for (listener in mMusicServiceEventListeners) {
+ listener.onFavoriteStateChanged()
+ }
+ }
+
+ override fun onHasPermissionsChanged(hasPermissions: Boolean) {
+ super.onHasPermissionsChanged(hasPermissions)
+ val intent = Intent(MEDIA_STORE_CHANGED)
+ intent.putExtra(
+ "from_permissions_changed",
+ true
+ ) // just in case we need to know this at some point
+ sendBroadcast(intent)
+ logD("sendBroadcast $hasPermissions")
+ }
+
+ override fun getPermissionsToRequest(): Array {
+ return mutableListOf().apply {
+ if (VersionUtils.hasT()) {
+ add(Manifest.permission.READ_MEDIA_AUDIO)
+ add(Manifest.permission.POST_NOTIFICATIONS)
+ } else {
+ add(Manifest.permission.READ_EXTERNAL_STORAGE)
+ }
+ if (!VersionUtils.hasR()) {
+ add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ }
+ }.toTypedArray()
+ }
+
+ private class MusicStateReceiver(activity: AbsMusicServiceActivity) : BroadcastReceiver() {
+
+ private val reference: WeakReference = WeakReference(activity)
+
+ override fun onReceive(context: Context, intent: Intent) {
+ val action = intent.action
+ val activity = reference.get()
+ if (activity != null && action != null) {
+ when (action) {
+ FAVORITE_STATE_CHANGED -> activity.onFavoriteStateChanged()
+ META_CHANGED -> activity.onPlayingMetaChanged()
+ QUEUE_CHANGED -> activity.onQueueChanged()
+ PLAY_STATE_CHANGED -> activity.onPlayStateChanged()
+ REPEAT_MODE_CHANGED -> activity.onRepeatModeChanged()
+ SHUFFLE_MODE_CHANGED -> activity.onShuffleModeChanged()
+ MEDIA_STORE_CHANGED -> activity.onMediaStoreChanged()
+ }
+ }
+ }
+ }
+
+ companion object {
+ val TAG: String = AbsMusicServiceActivity::class.java.simpleName
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt
new file mode 100644
index 000000000..2a81ca9b4
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt
@@ -0,0 +1,581 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.activities.base
+
+import android.animation.ArgbEvaluator
+import android.animation.ValueAnimator
+import android.content.Intent
+import android.content.SharedPreferences
+import android.content.res.ColorStateList
+import android.graphics.Color
+import android.os.Bundle
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewTreeObserver
+import android.view.animation.PathInterpolator
+import android.widget.FrameLayout
+import androidx.activity.OnBackPressedCallback
+import androidx.core.animation.doOnEnd
+import androidx.core.view.*
+import androidx.fragment.app.commit
+import code.name.monkey.appthemehelper.util.VersionUtils
+import code.name.monkey.retromusic.ADAPTIVE_COLOR_APP
+import code.name.monkey.retromusic.ALBUM_COVER_STYLE
+import code.name.monkey.retromusic.ALBUM_COVER_TRANSFORM
+import code.name.monkey.retromusic.CAROUSEL_EFFECT
+import code.name.monkey.retromusic.CIRCLE_PLAY_BUTTON
+import code.name.monkey.retromusic.EXTRA_SONG_INFO
+import code.name.monkey.retromusic.KEEP_SCREEN_ON
+import code.name.monkey.retromusic.LIBRARY_CATEGORIES
+import code.name.monkey.retromusic.NOW_PLAYING_SCREEN_ID
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.SCREEN_ON_LYRICS
+import code.name.monkey.retromusic.SWIPE_ANYWHERE_NOW_PLAYING
+import code.name.monkey.retromusic.SWIPE_DOWN_DISMISS
+import code.name.monkey.retromusic.TAB_TEXT_MODE
+import code.name.monkey.retromusic.TOGGLE_ADD_CONTROLS
+import code.name.monkey.retromusic.TOGGLE_FULL_SCREEN
+import code.name.monkey.retromusic.TOGGLE_VOLUME
+import code.name.monkey.retromusic.activities.PermissionActivity
+import code.name.monkey.retromusic.databinding.SlidingMusicPanelLayoutBinding
+import code.name.monkey.retromusic.extensions.*
+import code.name.monkey.retromusic.fragments.LibraryViewModel
+import code.name.monkey.retromusic.fragments.NowPlayingScreen
+import code.name.monkey.retromusic.fragments.NowPlayingScreen.*
+import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
+import code.name.monkey.retromusic.fragments.other.MiniPlayerFragment
+import code.name.monkey.retromusic.fragments.player.adaptive.AdaptiveFragment
+import code.name.monkey.retromusic.fragments.player.blur.BlurPlayerFragment
+import code.name.monkey.retromusic.fragments.player.card.CardFragment
+import code.name.monkey.retromusic.fragments.player.cardblur.CardBlurFragment
+import code.name.monkey.retromusic.fragments.player.circle.CirclePlayerFragment
+import code.name.monkey.retromusic.fragments.player.classic.ClassicPlayerFragment
+import code.name.monkey.retromusic.fragments.player.color.ColorFragment
+import code.name.monkey.retromusic.fragments.player.fit.FitFragment
+import code.name.monkey.retromusic.fragments.player.flat.FlatPlayerFragment
+import code.name.monkey.retromusic.fragments.player.full.FullPlayerFragment
+import code.name.monkey.retromusic.fragments.player.gradient.GradientPlayerFragment
+import code.name.monkey.retromusic.fragments.player.material.MaterialFragment
+import code.name.monkey.retromusic.fragments.player.md3.MD3PlayerFragment
+import code.name.monkey.retromusic.fragments.player.normal.PlayerFragment
+import code.name.monkey.retromusic.fragments.player.peek.PeekPlayerFragment
+import code.name.monkey.retromusic.fragments.player.plain.PlainPlayerFragment
+import code.name.monkey.retromusic.fragments.player.simple.SimplePlayerFragment
+import code.name.monkey.retromusic.fragments.player.tiny.TinyPlayerFragment
+import code.name.monkey.retromusic.fragments.queue.PlayingQueueFragment
+import code.name.monkey.retromusic.helper.MusicPlayerRemote
+import code.name.monkey.retromusic.model.CategoryInfo
+import code.name.monkey.retromusic.util.PreferenceUtil
+import code.name.monkey.retromusic.util.ViewUtil
+import code.name.monkey.retromusic.util.logD
+import com.google.android.material.bottomnavigation.BottomNavigationView
+import com.google.android.material.bottomsheet.BottomSheetBehavior
+import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
+import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED
+import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_DRAGGING
+import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
+import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN
+import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_SETTLING
+import com.google.android.material.bottomsheet.BottomSheetBehavior.from
+import org.koin.androidx.viewmodel.ext.android.viewModel
+
+
+abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity(),
+ SharedPreferences.OnSharedPreferenceChangeListener {
+ companion object {
+ val TAG: String = AbsSlidingMusicPanelActivity::class.java.simpleName
+ }
+
+ var fromNotification = false
+ private var windowInsets: WindowInsetsCompat? = null
+ protected val libraryViewModel by viewModel()
+ private lateinit var bottomSheetBehavior: BottomSheetBehavior
+ private lateinit var playerFragment: AbsPlayerFragment
+ private var miniPlayerFragment: MiniPlayerFragment? = null
+ private var nowPlayingScreen: NowPlayingScreen? = null
+ private var taskColor: Int = 0
+ private var paletteColor: Int = Color.WHITE
+ private var navigationBarColor = 0
+
+ private val panelState: Int
+ get() = bottomSheetBehavior.state
+ private lateinit var binding: SlidingMusicPanelLayoutBinding
+ private var isInOneTabMode = false
+
+ private var navigationBarColorAnimator: ValueAnimator? = null
+ private val argbEvaluator: ArgbEvaluator = ArgbEvaluator()
+
+ private val onBackPressedCallback = object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ println("Handle back press ${bottomSheetBehavior.state}")
+ if (!handleBackPress()) {
+ remove()
+ onBackPressedDispatcher.onBackPressed()
+ }
+ }
+ }
+
+ private val bottomSheetCallbackList by lazy {
+ object : BottomSheetCallback() {
+
+ override fun onSlide(bottomSheet: View, slideOffset: Float) {
+ setMiniPlayerAlphaProgress(slideOffset)
+ navigationBarColorAnimator?.cancel()
+ setNavigationBarColorPreOreo(
+ argbEvaluator.evaluate(
+ slideOffset,
+ surfaceColor(),
+ navigationBarColor
+ ) as Int
+ )
+ }
+
+ override fun onStateChanged(bottomSheet: View, newState: Int) {
+ onBackPressedCallback.isEnabled = newState == STATE_EXPANDED
+ when (newState) {
+ STATE_EXPANDED -> {
+ onPanelExpanded()
+ if (PreferenceUtil.lyricsScreenOn && PreferenceUtil.showLyrics) {
+ keepScreenOn(true)
+ }
+ }
+
+ STATE_COLLAPSED -> {
+ onPanelCollapsed()
+ if ((PreferenceUtil.lyricsScreenOn && PreferenceUtil.showLyrics) || !PreferenceUtil.isScreenOnEnabled) {
+ keepScreenOn(false)
+ }
+ }
+
+ STATE_SETTLING, STATE_DRAGGING -> {
+ if (fromNotification) {
+ binding.navigationView.bringToFront()
+ fromNotification = false
+ }
+ }
+
+ STATE_HIDDEN -> {
+ MusicPlayerRemote.clearQueue()
+ }
+
+ else -> {
+ logD("Do a flip")
+ }
+ }
+ }
+ }
+ }
+
+ fun getBottomSheetBehavior() = bottomSheetBehavior
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ if (!hasPermissions()) {
+ startActivity(Intent(this, PermissionActivity::class.java))
+ finish()
+ }
+ binding = SlidingMusicPanelLayoutBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ binding.root.setOnApplyWindowInsetsListener { _, insets ->
+ windowInsets = WindowInsetsCompat.toWindowInsetsCompat(insets)
+ insets
+ }
+ chooseFragmentForTheme()
+ setupSlidingUpPanel()
+ setupBottomSheet()
+ updateColor()
+ if (!PreferenceUtil.materialYou) {
+ binding.slidingPanel.backgroundTintList = ColorStateList.valueOf(darkAccentColor())
+ navigationView.backgroundTintList = ColorStateList.valueOf(darkAccentColor())
+ }
+
+ navigationBarColor = surfaceColor()
+
+ onBackPressedDispatcher.addCallback(onBackPressedCallback)
+ }
+
+ private fun setupBottomSheet() {
+ bottomSheetBehavior = from(binding.slidingPanel)
+ bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallbackList)
+ bottomSheetBehavior.isHideable = PreferenceUtil.swipeDownToDismiss
+ bottomSheetBehavior.significantVelocityThreshold = 300
+ setMiniPlayerAlphaProgress(0F)
+ }
+
+ override fun onResume() {
+ super.onResume()
+ PreferenceUtil.registerOnSharedPreferenceChangedListener(this)
+ if (nowPlayingScreen != PreferenceUtil.nowPlayingScreen) {
+ postRecreate()
+ }
+ if (bottomSheetBehavior.state == STATE_EXPANDED) {
+ setMiniPlayerAlphaProgress(1f)
+ }
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ bottomSheetBehavior.removeBottomSheetCallback(bottomSheetCallbackList)
+ PreferenceUtil.unregisterOnSharedPreferenceChangedListener(this)
+ }
+
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
+ when (key) {
+ SWIPE_DOWN_DISMISS -> {
+ bottomSheetBehavior.isHideable = PreferenceUtil.swipeDownToDismiss
+ }
+
+ TOGGLE_ADD_CONTROLS -> {
+ miniPlayerFragment?.setUpButtons()
+ }
+
+ NOW_PLAYING_SCREEN_ID -> {
+ chooseFragmentForTheme()
+ binding.slidingPanel.updateLayoutParams {
+ height = if (nowPlayingScreen != Peek) {
+ ViewGroup.LayoutParams.MATCH_PARENT
+ } else {
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ }
+ onServiceConnected()
+ }
+ }
+
+ ALBUM_COVER_TRANSFORM, CAROUSEL_EFFECT,
+ ALBUM_COVER_STYLE, TOGGLE_VOLUME, EXTRA_SONG_INFO, CIRCLE_PLAY_BUTTON,
+ -> {
+ chooseFragmentForTheme()
+ onServiceConnected()
+ }
+
+ SWIPE_ANYWHERE_NOW_PLAYING -> {
+ playerFragment.addSwipeDetector()
+ }
+
+ ADAPTIVE_COLOR_APP -> {
+ if (PreferenceUtil.nowPlayingScreen in listOf(Normal, Material, Flat)) {
+ chooseFragmentForTheme()
+ onServiceConnected()
+ }
+ }
+
+ LIBRARY_CATEGORIES -> {
+ updateTabs()
+ }
+
+ TAB_TEXT_MODE -> {
+ navigationView.labelVisibilityMode = PreferenceUtil.tabTitleMode
+ }
+
+ TOGGLE_FULL_SCREEN -> {
+ recreate()
+ }
+
+ SCREEN_ON_LYRICS -> {
+ keepScreenOn(bottomSheetBehavior.state == STATE_EXPANDED && PreferenceUtil.lyricsScreenOn && PreferenceUtil.showLyrics || PreferenceUtil.isScreenOnEnabled)
+ }
+
+ KEEP_SCREEN_ON -> {
+ maybeSetScreenOn()
+ }
+ }
+ }
+
+ fun collapsePanel() {
+ bottomSheetBehavior.state = STATE_COLLAPSED
+ }
+
+ fun expandPanel() {
+ bottomSheetBehavior.state = STATE_EXPANDED
+ }
+
+ private fun setMiniPlayerAlphaProgress(progress: Float) {
+ if (progress < 0) return
+ val alpha = 1 - progress
+ miniPlayerFragment?.view?.alpha = 1 - (progress / 0.2F)
+ miniPlayerFragment?.view?.isGone = alpha == 0f
+ if (!isLandscape) {
+ binding.navigationView.translationY = progress * 500
+ binding.navigationView.alpha = alpha
+ }
+ binding.playerFragmentContainer.alpha = (progress - 0.2F) / 0.2F
+ }
+
+ private fun animateNavigationBarColor(color: Int) {
+ if (VersionUtils.hasOreo()) return
+ navigationBarColorAnimator?.cancel()
+ navigationBarColorAnimator = ValueAnimator
+ .ofArgb(window.navigationBarColor, color).apply {
+ duration = ViewUtil.RETRO_MUSIC_ANIM_TIME.toLong()
+ interpolator = PathInterpolator(0.4f, 0f, 1f, 1f)
+ addUpdateListener { animation: ValueAnimator ->
+ setNavigationBarColorPreOreo(
+ animation.animatedValue as Int
+ )
+ }
+ start()
+ }
+ }
+
+ open fun onPanelCollapsed() {
+ setMiniPlayerAlphaProgress(0F)
+ // restore values
+ animateNavigationBarColor(surfaceColor())
+ setLightStatusBarAuto()
+ setLightNavigationBarAuto()
+ setTaskDescriptionColor(taskColor)
+ //playerFragment?.onHide()
+ }
+
+ open fun onPanelExpanded() {
+ setMiniPlayerAlphaProgress(1F)
+ onPaletteColorChanged()
+ //playerFragment?.onShow()
+ }
+
+ private fun setupSlidingUpPanel() {
+ binding.slidingPanel.viewTreeObserver.addOnGlobalLayoutListener(object :
+ ViewTreeObserver.OnGlobalLayoutListener {
+ override fun onGlobalLayout() {
+ binding.slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this)
+ if (nowPlayingScreen != Peek) {
+ binding.slidingPanel.updateLayoutParams {
+ height = ViewGroup.LayoutParams.MATCH_PARENT
+ }
+ }
+ when (panelState) {
+ STATE_EXPANDED -> onPanelExpanded()
+ STATE_COLLAPSED -> onPanelCollapsed()
+ else -> {
+ // playerFragment!!.onHide()
+ }
+ }
+ }
+ })
+ }
+
+ val navigationView get() = binding.navigationView
+
+ val slidingPanel get() = binding.slidingPanel
+
+ val isBottomNavVisible get() = navigationView.isVisible && navigationView is BottomNavigationView
+
+ override fun onServiceConnected() {
+ super.onServiceConnected()
+ hideBottomSheet(false)
+ }
+
+ override fun onQueueChanged() {
+ super.onQueueChanged()
+ // Mini player should be hidden in Playing Queue
+ // it may pop up if hideBottomSheet is called
+ if (currentFragment(R.id.fragment_container) !is PlayingQueueFragment) {
+ hideBottomSheet(MusicPlayerRemote.playingQueue.isEmpty())
+ }
+ }
+
+ private fun handleBackPress(): Boolean {
+ if (panelState == STATE_EXPANDED) {
+ collapsePanel()
+ return true
+ }
+ return false
+ }
+
+ private fun onPaletteColorChanged() {
+ if (panelState == STATE_EXPANDED) {
+ navigationBarColor = surfaceColor()
+ setTaskDescColor(paletteColor)
+ val isColorLight = paletteColor.isColorLight
+ if (PreferenceUtil.isAdaptiveColor && (nowPlayingScreen == Normal || nowPlayingScreen == Flat || nowPlayingScreen == Material)) {
+ setLightNavigationBar(true)
+ setLightStatusBar(isColorLight)
+ } else if (nowPlayingScreen == Card || nowPlayingScreen == Blur || nowPlayingScreen == BlurCard) {
+ animateNavigationBarColor(Color.BLACK)
+ navigationBarColor = Color.BLACK
+ setLightStatusBar(false)
+ setLightNavigationBar(true)
+ } else if (nowPlayingScreen == Color || nowPlayingScreen == Tiny || nowPlayingScreen == Gradient) {
+ animateNavigationBarColor(paletteColor)
+ navigationBarColor = paletteColor
+ setLightNavigationBar(isColorLight)
+ setLightStatusBar(isColorLight)
+ } else if (nowPlayingScreen == Full) {
+ animateNavigationBarColor(paletteColor)
+ navigationBarColor = paletteColor
+ setLightNavigationBar(isColorLight)
+ setLightStatusBar(false)
+ } else if (nowPlayingScreen == Classic) {
+ setLightStatusBar(false)
+ } else if (nowPlayingScreen == Fit) {
+ setLightStatusBar(false)
+ }
+ }
+ }
+
+ private fun setTaskDescColor(color: Int) {
+ taskColor = color
+ if (panelState == STATE_COLLAPSED) {
+ setTaskDescriptionColor(color)
+ }
+ }
+
+ fun updateTabs() {
+ binding.navigationView.menu.clear()
+ val currentTabs: List = PreferenceUtil.libraryCategory
+ for (tab in currentTabs) {
+ if (tab.visible) {
+ val menu = tab.category
+ binding.navigationView.menu.add(0, menu.id, 0, menu.stringRes)
+ .setIcon(menu.icon)
+ }
+ }
+ if (binding.navigationView.menu.size() == 1) {
+ isInOneTabMode = true
+ binding.navigationView.isVisible = false
+ } else {
+ isInOneTabMode = false
+ }
+ }
+
+ private fun updateColor() {
+ libraryViewModel.paletteColor.observe(this) { color ->
+ this.paletteColor = color
+ onPaletteColorChanged()
+ }
+ }
+
+ fun setBottomNavVisibility(
+ visible: Boolean,
+ animate: Boolean = false,
+ hideBottomSheet: Boolean = MusicPlayerRemote.playingQueue.isEmpty(),
+ ) {
+ if (!ViewCompat.isLaidOut(navigationView)) {
+ return
+ }
+ if (isInOneTabMode) {
+ hideBottomSheet(
+ hide = hideBottomSheet,
+ animate = animate,
+ isBottomNavVisible = false
+ )
+ return
+ }
+ if (visible xor navigationView.isVisible) {
+ val mAnimate = animate && bottomSheetBehavior.state == STATE_COLLAPSED
+ if (mAnimate) {
+ if (visible) {
+ binding.navigationView.bringToFront()
+ binding.navigationView.show()
+ } else {
+ binding.navigationView.hide()
+ }
+ } else {
+ binding.navigationView.isVisible = visible
+ if (visible && bottomSheetBehavior.state != STATE_EXPANDED) {
+ binding.navigationView.bringToFront()
+ }
+ }
+ }
+ hideBottomSheet(
+ hide = hideBottomSheet,
+ animate = animate,
+ isBottomNavVisible = visible && navigationView is BottomNavigationView
+ )
+ }
+
+ fun hideBottomSheet(
+ hide: Boolean,
+ animate: Boolean = false,
+ isBottomNavVisible: Boolean = navigationView.isVisible && navigationView is BottomNavigationView,
+ ) {
+ val heightOfBar = windowInsets.getBottomInsets() + dip(R.dimen.mini_player_height)
+ val heightOfBarWithTabs = heightOfBar + dip(R.dimen.bottom_nav_height)
+ if (hide) {
+ bottomSheetBehavior.peekHeight = -windowInsets.getBottomInsets()
+ bottomSheetBehavior.state = STATE_COLLAPSED
+ libraryViewModel.setFabMargin(
+ this,
+ if (isBottomNavVisible) dip(R.dimen.bottom_nav_height) else 0
+ )
+ } else {
+ if (MusicPlayerRemote.playingQueue.isNotEmpty()) {
+ binding.slidingPanel.elevation = 0F
+ binding.navigationView.elevation = 5F
+ if (isBottomNavVisible) {
+ logD("List")
+ if (animate) {
+ bottomSheetBehavior.peekHeightAnimate(heightOfBarWithTabs)
+ } else {
+ bottomSheetBehavior.peekHeight = heightOfBarWithTabs
+ }
+ libraryViewModel.setFabMargin(
+ this,
+ dip(R.dimen.bottom_nav_mini_player_height)
+ )
+ } else {
+ logD("Details")
+ if (animate) {
+ bottomSheetBehavior.peekHeightAnimate(heightOfBar).doOnEnd {
+ binding.slidingPanel.bringToFront()
+ }
+ } else {
+ bottomSheetBehavior.peekHeight = heightOfBar
+ binding.slidingPanel.bringToFront()
+ }
+ libraryViewModel.setFabMargin(this, dip(R.dimen.mini_player_height))
+ }
+ }
+ }
+ }
+
+ fun setAllowDragging(allowDragging: Boolean) {
+ bottomSheetBehavior.isDraggable = allowDragging
+ hideBottomSheet(false)
+ }
+
+ private fun chooseFragmentForTheme() {
+ nowPlayingScreen = PreferenceUtil.nowPlayingScreen
+
+ val fragment: AbsPlayerFragment = when (nowPlayingScreen) {
+ Blur -> BlurPlayerFragment()
+ Adaptive -> AdaptiveFragment()
+ Normal -> PlayerFragment()
+ Card -> CardFragment()
+ BlurCard -> CardBlurFragment()
+ Fit -> FitFragment()
+ Flat -> FlatPlayerFragment()
+ Full -> FullPlayerFragment()
+ Plain -> PlainPlayerFragment()
+ Simple -> SimplePlayerFragment()
+ Material -> MaterialFragment()
+ Color -> ColorFragment()
+ Gradient -> GradientPlayerFragment()
+ Tiny -> TinyPlayerFragment()
+ Peek -> PeekPlayerFragment()
+ Circle -> CirclePlayerFragment()
+ Classic -> ClassicPlayerFragment()
+ MD3 -> MD3PlayerFragment()
+ else -> PlayerFragment()
+ } // must extend AbsPlayerFragment
+ supportFragmentManager.commit {
+ replace(R.id.playerFragmentContainer, fragment)
+ }
+ supportFragmentManager.executePendingTransactions()
+ playerFragment = whichFragment(R.id.playerFragmentContainer)
+ miniPlayerFragment = whichFragment(R.id.miniPlayerFragment)
+ miniPlayerFragment?.view?.setOnClickListener { expandPanel() }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsThemeActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsThemeActivity.kt
new file mode 100644
index 000000000..32b025f01
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsThemeActivity.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.activities.base
+
+import android.content.Context
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.view.KeyEvent
+import androidx.appcompat.app.AppCompatDelegate
+import androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode
+import androidx.core.os.LocaleListCompat
+import code.name.monkey.appthemehelper.common.ATHToolbarActivity
+import code.name.monkey.appthemehelper.util.VersionUtils
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.extensions.*
+import code.name.monkey.retromusic.util.PreferenceUtil
+import code.name.monkey.retromusic.util.theme.getNightMode
+import code.name.monkey.retromusic.util.theme.getThemeResValue
+
+abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable {
+
+ private val handler = Handler(Looper.getMainLooper())
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ updateLocale()
+ updateTheme()
+ hideStatusBar()
+ super.onCreate(savedInstanceState)
+ setEdgeToEdgeOrImmersive()
+ maybeSetScreenOn()
+ setLightNavigationBarAuto()
+ setLightStatusBarAuto(surfaceColor())
+ if (VersionUtils.hasQ()) {
+ window.decorView.isForceDarkAllowed = false
+ }
+ }
+
+ private fun updateTheme() {
+ setTheme(getThemeResValue())
+ if (PreferenceUtil.materialYou) {
+ setDefaultNightMode(getNightMode())
+ }
+
+ if (PreferenceUtil.isCustomFont) {
+ setTheme(R.style.FontThemeOverlay)
+ }
+ }
+
+ private fun updateLocale() {
+ val localeCode = PreferenceUtil.languageCode
+ if (PreferenceUtil.isLocaleAutoStorageEnabled) {
+ AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(localeCode))
+ PreferenceUtil.isLocaleAutoStorageEnabled = true
+ }
+ }
+
+ override fun onWindowFocusChanged(hasFocus: Boolean) {
+ super.onWindowFocusChanged(hasFocus)
+ if (hasFocus) {
+ hideStatusBar()
+ handler.removeCallbacks(this)
+ handler.postDelayed(this, 300)
+ } else {
+ handler.removeCallbacks(this)
+ }
+ }
+
+ override fun run() {
+ setImmersiveFullscreen()
+ }
+
+ override fun onStop() {
+ handler.removeCallbacks(this)
+ super.onStop()
+ }
+
+ public override fun onDestroy() {
+ super.onDestroy()
+ exitFullscreen()
+ }
+
+ override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
+ handler.removeCallbacks(this)
+ handler.postDelayed(this, 500)
+ }
+ return super.onKeyDown(keyCode, event)
+ }
+
+ override fun attachBaseContext(newBase: Context?) {
+ super.attachBaseContext(newBase)
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/BugReportActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/BugReportActivity.kt
new file mode 100644
index 000000000..e99881953
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/BugReportActivity.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.activities.bugreport
+
+import android.content.ClipData
+import android.content.ClipboardManager
+import android.content.Intent
+import android.os.Bundle
+import android.view.MenuItem
+import androidx.core.content.getSystemService
+import androidx.core.net.toUri
+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.activities.base.AbsThemeActivity
+import code.name.monkey.retromusic.activities.bugreport.model.DeviceInfo
+import code.name.monkey.retromusic.databinding.ActivityBugReportBinding
+import code.name.monkey.retromusic.extensions.accentColor
+import code.name.monkey.retromusic.extensions.setTaskDescriptionColorAuto
+import code.name.monkey.retromusic.extensions.showToast
+
+open class BugReportActivity : AbsThemeActivity() {
+
+ private lateinit var binding: ActivityBugReportBinding
+ private var deviceInfo: DeviceInfo? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityBugReportBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ setTaskDescriptionColorAuto()
+
+ initViews()
+
+ if (title.isNullOrEmpty()) setTitle(R.string.report_an_issue)
+
+ deviceInfo = DeviceInfo(this)
+ binding.cardDeviceInfo.airTextDeviceInfo.text = deviceInfo.toString()
+ }
+
+ private fun initViews() {
+ val accentColor = accentColor()
+ setSupportActionBar(binding.toolbar)
+ ToolbarContentTintHelper.colorBackButton(binding.toolbar)
+ supportActionBar?.setDisplayHomeAsUpEnabled(true)
+
+ binding.cardDeviceInfo.airTextDeviceInfo.setOnClickListener { copyDeviceInfoToClipBoard() }
+
+ TintHelper.setTintAuto(binding.sendFab, accentColor, true)
+ binding.sendFab.setOnClickListener { reportIssue() }
+ }
+
+ private fun reportIssue() {
+ copyDeviceInfoToClipBoard()
+ val i = Intent(Intent.ACTION_VIEW)
+ i.data = ISSUE_TRACKER_LINK.toUri()
+ i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ startActivity(i)
+ }
+
+ private fun copyDeviceInfoToClipBoard() {
+ val clipboard = getSystemService()
+ val clip = ClipData.newPlainText(getString(R.string.device_info), deviceInfo?.toMarkdown())
+ clipboard?.setPrimaryClip(clip)
+ showToast(R.string.copied_device_info_to_clipboard)
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ if (item.itemId == android.R.id.home) {
+ onBackPressedDispatcher.onBackPressed()
+ }
+ return super.onOptionsItemSelected(item)
+ }
+
+ companion object {
+ private const val ISSUE_TRACKER_LINK =
+ "https://github.com/MuntashirAkon/Metro/issues/new"
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/DeviceInfo.kt b/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/DeviceInfo.kt
new file mode 100644
index 000000000..19aff73a2
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/DeviceInfo.kt
@@ -0,0 +1,111 @@
+package code.name.monkey.retromusic.activities.bugreport.model
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
+import androidx.annotation.IntRange
+import androidx.appcompat.app.AppCompatDelegate
+import androidx.core.content.pm.PackageInfoCompat
+import code.name.monkey.retromusic.util.PreferenceUtil
+import code.name.monkey.retromusic.util.PreferenceUtil.isAdaptiveColor
+import code.name.monkey.retromusic.util.PreferenceUtil.nowPlayingScreen
+import java.util.*
+
+class DeviceInfo(context: Context) {
+ @SuppressLint("NewApi")
+ private val abis = Build.SUPPORTED_ABIS
+
+ @SuppressLint("NewApi")
+ private val abis32Bits = Build.SUPPORTED_32_BIT_ABIS
+
+ @SuppressLint("NewApi")
+ private val abis64Bits = Build.SUPPORTED_64_BIT_ABIS
+ private val baseTheme: String
+ private val brand = Build.BRAND
+ private val buildID = Build.DISPLAY
+ private val buildVersion = Build.VERSION.INCREMENTAL
+ private val device = Build.DEVICE
+ private val hardware = Build.HARDWARE
+ private val isAdaptive: Boolean
+ private val manufacturer = Build.MANUFACTURER
+ private val model = Build.MODEL
+ private val nowPlayingTheme: String
+ private val product = Build.PRODUCT
+ private val releaseVersion = Build.VERSION.RELEASE
+
+ @IntRange(from = 0)
+ private val sdkVersion = Build.VERSION.SDK_INT
+ private var versionCode = 0L
+ private var versionName: String? = null
+ private val selectedLang: String
+ fun toMarkdown(): String {
+ return """
+ Device info:
+ ---
+
+ App version $versionName
+ App version code $versionCode
+ Android build version $buildVersion
+ Android release version $releaseVersion
+ Android SDK version $sdkVersion
+ Android build ID $buildID
+ Device brand $brand
+ Device manufacturer $manufacturer
+ Device name $device
+ Device model $model
+ Device product name $product
+ Device hardware name $hardware
+ ABIs ${Arrays.toString(abis)}
+ ABIs (32bit) ${Arrays.toString(abis32Bits)}
+ ABIs (64bit) ${Arrays.toString(abis64Bits)}
+ Language $selectedLang
+
+
+ """.trimIndent()
+ }
+
+ override fun toString(): String {
+ return """
+ App version: $versionName
+ App version code: $versionCode
+ Android build version: $buildVersion
+ Android release version: $releaseVersion
+ Android SDK version: $sdkVersion
+ Android build ID: $buildID
+ Device brand: $brand
+ Device manufacturer: $manufacturer
+ Device name: $device
+ Device model: $model
+ Device product name: $product
+ Device hardware name: $hardware
+ ABIs: ${Arrays.toString(abis)}
+ ABIs (32bit): ${Arrays.toString(abis32Bits)}
+ ABIs (64bit): ${Arrays.toString(abis64Bits)}
+ Base theme: $baseTheme
+ Now playing theme: $nowPlayingTheme
+ Adaptive: $isAdaptive
+ System language: ${Locale.getDefault().toLanguageTag()}
+ In-App Language: $selectedLang
+ """.trimIndent()
+ }
+
+ init {
+ val packageInfo = try {
+ context.packageManager.getPackageInfo(context.packageName, 0)
+ } catch (e: PackageManager.NameNotFoundException) {
+ null
+ }
+ if (packageInfo != null) {
+ versionCode = PackageInfoCompat.getLongVersionCode(packageInfo)
+ versionName = packageInfo.versionName
+ } else {
+ versionCode = -1
+ versionName = null
+ }
+ baseTheme = PreferenceUtil.baseTheme
+ nowPlayingTheme = context.getString(nowPlayingScreen.titleRes)
+ isAdaptive = isAdaptiveColor
+ selectedLang = AppCompatDelegate.getApplicationLocales().toLanguageTags()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/saf/SAFGuideActivity.java b/app/src/main/java/code/name/monkey/retromusic/activities/saf/SAFGuideActivity.java
new file mode 100644
index 000000000..efd56210e
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/activities/saf/SAFGuideActivity.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2019 Hemanth Savarala.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by
+ * the Free Software Foundation either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ */
+
+package code.name.monkey.retromusic.activities.saf;
+
+import android.os.Build;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+
+import com.heinrichreimersoftware.materialintro.app.IntroActivity;
+import com.heinrichreimersoftware.materialintro.slide.SimpleSlide;
+
+import code.name.monkey.retromusic.R;
+
+/** Created by hemanths on 2019-07-31. */
+public class SAFGuideActivity extends IntroActivity {
+
+ public static final int REQUEST_CODE_SAF_GUIDE = 98;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setButtonCtaVisible(false);
+ setButtonNextVisible(false);
+ setButtonBackVisible(false);
+
+ setButtonCtaTintMode(BUTTON_CTA_TINT_MODE_TEXT);
+
+ String title =
+ String.format(getString(R.string.saf_guide_slide1_title), getString(R.string.app_name));
+
+ addSlide(
+ new SimpleSlide.Builder()
+ .title(title)
+ .description(
+ Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1
+ ? R.string.saf_guide_slide1_description_before_o
+ : R.string.saf_guide_slide1_description)
+ .image(R.drawable.saf_guide_1)
+ .background(code.name.monkey.appthemehelper.R.color.md_deep_purple_300)
+ .backgroundDark(code.name.monkey.appthemehelper.R.color.md_deep_purple_400)
+ .layout(R.layout.fragment_simple_slide_large_image)
+ .build());
+ addSlide(
+ new SimpleSlide.Builder()
+ .title(R.string.saf_guide_slide2_title)
+ .description(R.string.saf_guide_slide2_description)
+ .image(R.drawable.saf_guide_2)
+ .background(code.name.monkey.appthemehelper.R.color.md_deep_purple_500)
+ .backgroundDark(code.name.monkey.appthemehelper.R.color.md_deep_purple_600)
+ .layout(R.layout.fragment_simple_slide_large_image)
+ .build());
+ addSlide(
+ new SimpleSlide.Builder()
+ .title(R.string.saf_guide_slide3_title)
+ .description(R.string.saf_guide_slide3_description)
+ .image(R.drawable.saf_guide_3)
+ .background(code.name.monkey.appthemehelper.R.color.md_deep_purple_700)
+ .backgroundDark(code.name.monkey.appthemehelper.R.color.md_deep_purple_800)
+ .layout(R.layout.fragment_simple_slide_large_image)
+ .build());
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/saf/SAFRequestActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/saf/SAFRequestActivity.kt
new file mode 100644
index 000000000..1b389b9d2
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/activities/saf/SAFRequestActivity.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2021 Bartlomiej Uliasz.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by
+ * the Free Software Foundation either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ */
+package code.name.monkey.retromusic.activities.saf
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import code.name.monkey.retromusic.activities.saf.SAFGuideActivity.REQUEST_CODE_SAF_GUIDE
+import code.name.monkey.retromusic.util.SAFUtil
+
+/** Created by buliasz on 2021-02-07. */
+class SAFRequestActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val intent = Intent(this, code.name.monkey.retromusic.activities.saf.SAFGuideActivity::class.java)
+ startActivityForResult(intent, REQUEST_CODE_SAF_GUIDE)
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
+ super.onActivityResult(requestCode, resultCode, intent)
+ when (requestCode) {
+ REQUEST_CODE_SAF_GUIDE -> {
+ SAFUtil.openTreePicker(this)
+ }
+ SAFUtil.REQUEST_SAF_PICK_TREE -> {
+ if (resultCode == RESULT_OK) {
+ SAFUtil.saveTreeUri(this, intent)
+ }
+ finish()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AbsTagEditorActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AbsTagEditorActivity.kt
new file mode 100755
index 000000000..3a9ca4e4b
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AbsTagEditorActivity.kt
@@ -0,0 +1,457 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.activities.tageditor
+
+import android.app.Activity
+import android.app.SearchManager
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.net.Uri
+import android.os.Bundle
+import android.provider.MediaStore
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.MenuItem
+import android.view.animation.OvershootInterpolator
+import android.widget.ImageView
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.IntentSenderRequest
+import androidx.activity.result.PickVisualMediaRequest
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.AlertDialog
+import androidx.lifecycle.lifecycleScope
+import androidx.viewbinding.ViewBinding
+import code.name.monkey.appthemehelper.util.VersionUtils
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.activities.base.AbsBaseActivity
+import code.name.monkey.retromusic.extensions.accentColor
+import code.name.monkey.retromusic.extensions.colorButtons
+import code.name.monkey.retromusic.extensions.hideSoftKeyboard
+import code.name.monkey.retromusic.extensions.setTaskDescriptionColorAuto
+import code.name.monkey.retromusic.model.ArtworkInfo
+import code.name.monkey.retromusic.model.AudioTagInfo
+import code.name.monkey.retromusic.repository.Repository
+import code.name.monkey.retromusic.util.logD
+import code.name.monkey.retromusic.util.logE
+import com.google.android.material.button.MaterialButton
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import org.jaudiotagger.audio.AudioFile
+import org.jaudiotagger.audio.AudioFileIO
+import org.jaudiotagger.tag.FieldKey
+import org.koin.android.ext.android.inject
+import java.io.File
+
+abstract class AbsTagEditorActivity : AbsBaseActivity() {
+ abstract val editorImage: ImageView
+ val repository by inject()
+
+ lateinit var saveFab: MaterialButton
+ protected var id: Long = 0
+ private set
+ private var paletteColorPrimary: Int = 0
+ private var songPaths: List? = null
+ private var savedSongPaths: List? = null
+ private val currentSongPath: String? = null
+ private var savedTags: Map? = null
+ private var savedArtworkInfo: ArtworkInfo? = null
+ private var _binding: VB? = null
+ protected val binding: VB get() = _binding!!
+ private var cacheFiles = listOf()
+
+ abstract val bindingInflater: (LayoutInflater) -> VB
+
+ private lateinit var launcher: ActivityResultLauncher
+
+ protected abstract fun loadImageFromFile(selectedFile: Uri?)
+
+ protected val show: AlertDialog
+ get() =
+ MaterialAlertDialogBuilder(this)
+ .setTitle(R.string.update_image)
+ .setItems(items.toTypedArray()) { _, position ->
+ when (position) {
+ 0 -> startImagePicker()
+ 1 -> searchImageOnWeb()
+ 2 -> deleteImage()
+ }
+ }
+ .setNegativeButton(R.string.action_cancel, null)
+ .show()
+ .colorButtons()
+
+ internal val albumArtist: String?
+ get() {
+ return try {
+ getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.ALBUM_ARTIST)
+ } catch (e: Exception) {
+ logE(e)
+ null
+ }
+ }
+
+ protected val songTitle: String?
+ get() {
+ return try {
+ getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.TITLE)
+ } catch (e: Exception) {
+ logE(e)
+ null
+ }
+ }
+ protected val composer: String?
+ get() {
+ return try {
+ getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.COMPOSER)
+ } catch (e: Exception) {
+ logE(e)
+ null
+ }
+ }
+
+ protected val albumTitle: String?
+ get() {
+ return try {
+ getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.ALBUM)
+ } catch (e: Exception) {
+ logE(e)
+ null
+ }
+ }
+
+ protected val artistName: String?
+ get() {
+ return try {
+ getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.ARTIST)
+ } catch (e: Exception) {
+ logE(e)
+ null
+ }
+ }
+
+ protected val albumArtistName: String?
+ get() {
+ return try {
+ getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.ALBUM_ARTIST)
+ } catch (e: Exception) {
+ logE(e)
+ null
+ }
+ }
+
+ protected val genreName: String?
+ get() {
+ return try {
+ getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.GENRE)
+ } catch (e: Exception) {
+ logE(e)
+ null
+ }
+ }
+
+ protected val songYear: String?
+ get() {
+ return try {
+ getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.YEAR)
+ } catch (e: Exception) {
+ logE(e)
+ null
+ }
+ }
+
+ protected val trackNumber: String?
+ get() {
+ return try {
+ getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.TRACK)
+ } catch (e: Exception) {
+ logE(e)
+ null
+ }
+ }
+
+ protected val discNumber: String?
+ get() {
+ return try {
+ getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.DISC_NO)
+ } catch (e: Exception) {
+ logE(e)
+ null
+ }
+ }
+
+ protected val lyrics: String?
+ get() {
+ return try {
+ getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.LYRICS)
+ } catch (e: Exception) {
+ logE(e)
+ null
+ }
+ }
+
+ protected val albumArt: Bitmap?
+ get() {
+ try {
+ val artworkTag = getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.firstArtwork
+ if (artworkTag != null) {
+ val artworkBinaryData = artworkTag.binaryData
+ return BitmapFactory.decodeByteArray(
+ artworkBinaryData,
+ 0,
+ artworkBinaryData.size
+ )
+ }
+ return null
+ } catch (e: Exception) {
+ logE(e)
+ return null
+ }
+ }
+
+ private val pickArtworkImage =
+ registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
+ loadImageFromFile(uri)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ _binding = bindingInflater.invoke(layoutInflater)
+ setContentView(binding.root)
+ setTaskDescriptionColorAuto()
+
+ saveFab = findViewById(R.id.saveTags)
+ getIntentExtras()
+
+ songPaths = getSongPaths()
+ logD(songPaths?.size)
+ if (songPaths!!.isEmpty()) {
+ finish()
+ }
+ setUpViews()
+ launcher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
+ if (it.resultCode == Activity.RESULT_OK) {
+ writeToFiles(getSongUris(), cacheFiles)
+ }
+ }
+ }
+
+ private fun setUpViews() {
+ setUpFab()
+ setUpImageView()
+ }
+
+ private lateinit var items: List
+
+ private fun setUpImageView() {
+ loadCurrentImage()
+ items = listOf(
+ getString(R.string.pick_from_local_storage),
+ getString(R.string.web_search),
+ getString(R.string.remove_cover)
+ )
+ editorImage.setOnClickListener { show }
+ }
+
+ private fun startImagePicker() {
+ pickArtworkImage.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
+ }
+
+ protected abstract fun loadCurrentImage()
+
+ protected abstract fun searchImageOnWeb()
+
+ protected abstract fun deleteImage()
+
+ private fun setUpFab() {
+ saveFab.accentColor()
+ saveFab.apply {
+ scaleX = 0f
+ scaleY = 0f
+ isEnabled = false
+ setOnClickListener { save() }
+ }
+ }
+
+ protected abstract fun save()
+
+ private fun getIntentExtras() {
+ val intentExtras = intent.extras
+ if (intentExtras != null) {
+ id = intentExtras.getLong(EXTRA_ID)
+ }
+ }
+
+ protected abstract fun getSongPaths(): List
+
+ protected abstract fun getSongUris(): List
+
+ protected fun searchWebFor(vararg keys: String) {
+ val stringBuilder = StringBuilder()
+ for (key in keys) {
+ stringBuilder.append(key)
+ stringBuilder.append(" ")
+ }
+ val intent = Intent(Intent.ACTION_WEB_SEARCH)
+ intent.putExtra(SearchManager.QUERY, stringBuilder.toString())
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
+ startActivity(intent)
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ android.R.id.home -> {
+ onBackPressedDispatcher.onBackPressed()
+ return true
+ }
+ }
+ return super.onOptionsItemSelected(item)
+ }
+
+ protected fun dataChanged() {
+ showFab()
+ }
+
+ private fun showFab() {
+ saveFab.animate().setDuration(500).setInterpolator(OvershootInterpolator()).scaleX(1f)
+ .scaleY(1f).start()
+ saveFab.isEnabled = true
+ }
+
+ private fun hideFab() {
+ saveFab.animate().setDuration(500).setInterpolator(OvershootInterpolator()).scaleX(0.0f)
+ .scaleY(0.0f).start()
+ saveFab.isEnabled = false
+ }
+
+ protected fun setImageBitmap(bitmap: Bitmap?, bgColor: Int) {
+ if (bitmap == null) {
+ editorImage.setImageResource(R.drawable.default_audio_art)
+ } else {
+ editorImage.setImageBitmap(bitmap)
+ }
+ setColors(bgColor)
+ }
+
+ protected open fun setColors(color: Int) {
+ paletteColorPrimary = color
+ }
+
+ protected fun writeValuesToFiles(
+ fieldKeyValueMap: Map,
+ artworkInfo: ArtworkInfo?
+ ) {
+ hideSoftKeyboard()
+
+ hideFab()
+ logD(fieldKeyValueMap)
+ GlobalScope.launch {
+ if (VersionUtils.hasR()) {
+ cacheFiles = TagWriter.writeTagsToFilesR(
+ this@AbsTagEditorActivity, AudioTagInfo(
+ songPaths,
+ fieldKeyValueMap,
+ artworkInfo
+ )
+ )
+
+ if (cacheFiles.isNotEmpty()) {
+ val pendingIntent =
+ MediaStore.createWriteRequest(contentResolver, getSongUris())
+ launcher.launch(IntentSenderRequest.Builder(pendingIntent).build())
+ }
+ } else {
+ TagWriter.writeTagsToFiles(
+ this@AbsTagEditorActivity, AudioTagInfo(
+ songPaths,
+ fieldKeyValueMap,
+ artworkInfo
+ )
+ )
+ }
+ }
+ }
+
+ private fun writeTags(paths: List?) {
+ GlobalScope.launch {
+ if (VersionUtils.hasR()) {
+ cacheFiles = TagWriter.writeTagsToFilesR(
+ this@AbsTagEditorActivity, AudioTagInfo(
+ paths,
+ savedTags,
+ savedArtworkInfo
+ )
+ )
+ val pendingIntent = MediaStore.createWriteRequest(contentResolver, getSongUris())
+
+ launcher.launch(IntentSenderRequest.Builder(pendingIntent).build())
+ } else {
+ TagWriter.writeTagsToFiles(
+ this@AbsTagEditorActivity, AudioTagInfo(
+ paths,
+ savedTags,
+ savedArtworkInfo
+ )
+ )
+ }
+ }
+ }
+
+ private lateinit var audioFile: AudioFile
+
+ private fun getAudioFile(path: String): AudioFile {
+ return try {
+ if (!this::audioFile.isInitialized) {
+ audioFile = AudioFileIO.read(File(path))
+ }
+ audioFile
+ } catch (e: Exception) {
+ Log.e(TAG, "Could not read audio file $path", e)
+ AudioFile()
+ }
+ }
+
+ private fun writeToFiles(songUris: List, cacheFiles: List) {
+ if (cacheFiles.size == songUris.size) {
+ for (i in cacheFiles.indices) {
+ contentResolver.openOutputStream(songUris[i])?.use { output ->
+ cacheFiles[i].inputStream().use { input ->
+ input.copyTo(output)
+ }
+ }
+ }
+ }
+ lifecycleScope.launch {
+ TagWriter.scan(this@AbsTagEditorActivity, getSongPaths())
+ }
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ // Delete Cache Files
+ cacheFiles.forEach { file ->
+ file.delete()
+ }
+ }
+
+ companion object {
+ const val EXTRA_ID = "extra_id"
+ const val EXTRA_PALETTE = "extra_palette"
+ private val TAG = AbsTagEditorActivity::class.java.simpleName
+ private const val REQUEST_CODE_SELECT_IMAGE = 1000
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AlbumTagEditorActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AlbumTagEditorActivity.kt
new file mode 100755
index 000000000..2c9013657
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AlbumTagEditorActivity.kt
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.activities.tageditor
+
+import android.app.Activity
+import android.content.res.ColorStateList
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import android.os.Bundle
+import android.transition.Slide
+import android.view.LayoutInflater
+import android.widget.ImageView
+import android.widget.Toast
+import androidx.core.widget.doAfterTextChanged
+import code.name.monkey.appthemehelper.util.MaterialValueHelper
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.databinding.ActivityAlbumTagEditorBinding
+import code.name.monkey.retromusic.extensions.*
+import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
+import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
+import code.name.monkey.retromusic.model.ArtworkInfo
+import code.name.monkey.retromusic.model.Song
+import code.name.monkey.retromusic.util.ImageUtil
+import code.name.monkey.retromusic.util.MusicUtil
+import code.name.monkey.retromusic.util.RetroColorUtil.generatePalette
+import code.name.monkey.retromusic.util.RetroColorUtil.getColor
+import code.name.monkey.retromusic.util.logD
+import com.bumptech.glide.Glide
+import com.bumptech.glide.load.engine.DiskCacheStrategy
+import com.bumptech.glide.request.target.ImageViewTarget
+import com.bumptech.glide.request.transition.Transition
+import com.google.android.material.shape.MaterialShapeDrawable
+import org.jaudiotagger.tag.FieldKey
+import java.util.*
+
+class AlbumTagEditorActivity : AbsTagEditorActivity() {
+
+ override val bindingInflater: (LayoutInflater) -> ActivityAlbumTagEditorBinding =
+ ActivityAlbumTagEditorBinding::inflate
+
+ private fun windowEnterTransition() {
+ val slide = Slide()
+ slide.excludeTarget(R.id.appBarLayout, true)
+ slide.excludeTarget(R.id.status_bar, true)
+ slide.excludeTarget(android.R.id.statusBarBackground, true)
+ slide.excludeTarget(android.R.id.navigationBarBackground, true)
+
+ window.enterTransition = slide
+ }
+
+ private var albumArtBitmap: Bitmap? = null
+ private var deleteAlbumArt: Boolean = false
+
+ private fun setupToolbar() {
+ setSupportActionBar(binding.toolbar)
+ binding.appBarLayout?.statusBarForeground =
+ MaterialShapeDrawable.createWithElevationOverlay(this)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ window.sharedElementsUseOverlay = true
+ binding.imageContainer.transitionName = getString(R.string.transition_album_art)
+ windowEnterTransition()
+ setUpViews()
+ setupToolbar()
+ }
+
+ private fun setUpViews() {
+ fillViewsWithFileTags()
+
+ binding.yearContainer.setTint(false)
+ binding.genreContainer.setTint(false)
+ binding.albumTitleContainer.setTint(false)
+ binding.albumArtistContainer.setTint(false)
+
+ binding.albumText.appHandleColor().doAfterTextChanged { dataChanged() }
+ binding.albumArtistText.appHandleColor().doAfterTextChanged { dataChanged() }
+ binding.genreTitle.appHandleColor().doAfterTextChanged { dataChanged() }
+ binding.yearTitle.appHandleColor().doAfterTextChanged { dataChanged() }
+ }
+
+ private fun fillViewsWithFileTags() {
+ binding.albumText.setText(albumTitle)
+ binding.albumArtistText.setText(albumArtistName)
+ binding.genreTitle.setText(genreName)
+ binding.yearTitle.setText(songYear)
+ logD(albumTitle + albumArtistName)
+ }
+
+ override fun loadCurrentImage() {
+ val bitmap = albumArt
+ setImageBitmap(
+ bitmap,
+ getColor(
+ generatePalette(bitmap),
+ defaultFooterColor()
+ )
+ )
+ deleteAlbumArt = false
+ }
+
+ private fun toastLoadingFailed() {
+ showToast(R.string.could_not_download_album_cover)
+ }
+
+ override fun searchImageOnWeb() {
+ searchWebFor(binding.albumText.text.toString(), binding.albumArtistText.text.toString())
+ }
+
+ override fun deleteImage() {
+ setImageBitmap(
+ BitmapFactory.decodeResource(resources, R.drawable.default_audio_art),
+ defaultFooterColor()
+ )
+ deleteAlbumArt = true
+ dataChanged()
+ }
+
+ override fun loadImageFromFile(selectedFile: Uri?) {
+ Glide.with(this@AlbumTagEditorActivity)
+ .asBitmapPalette()
+ .load(selectedFile)
+ .diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true)
+ .into(object : ImageViewTarget(binding.editorImage) {
+ override fun onResourceReady(
+ resource: BitmapPaletteWrapper,
+ transition: Transition?
+ ) {
+ getColor(resource.palette, Color.TRANSPARENT)
+ albumArtBitmap = resource.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) }
+ setImageBitmap(
+ albumArtBitmap,
+ getColor(
+ resource.palette,
+ defaultFooterColor()
+ )
+ )
+ deleteAlbumArt = false
+ dataChanged()
+ setResult(Activity.RESULT_OK)
+ }
+
+ override fun onLoadFailed(errorDrawable: Drawable?) {
+ super.onLoadFailed(errorDrawable)
+ showToast(R.string.error_load_failed, Toast.LENGTH_LONG)
+ }
+
+ override fun setResource(resource: BitmapPaletteWrapper?) {}
+ })
+ }
+
+ override fun save() {
+ val fieldKeyValueMap = EnumMap(FieldKey::class.java)
+ fieldKeyValueMap[FieldKey.ALBUM] = binding.albumText.text.toString()
+ // android seems not to recognize album_artist field so we additionally write the normal artist field
+ fieldKeyValueMap[FieldKey.ARTIST] = binding.albumArtistText.text.toString()
+ fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = binding.albumArtistText.text.toString()
+ fieldKeyValueMap[FieldKey.GENRE] = binding.genreTitle.text.toString()
+ fieldKeyValueMap[FieldKey.YEAR] = binding.yearTitle.text.toString()
+
+ writeValuesToFiles(
+ fieldKeyValueMap,
+ when {
+ deleteAlbumArt -> ArtworkInfo(id, null)
+ albumArtBitmap == null -> null
+ else -> ArtworkInfo(id, albumArtBitmap!!)
+ }
+ )
+ }
+
+ override fun getSongPaths(): List {
+ return repository.albumById(id).songs
+ .map(Song::data)
+ }
+
+ override fun getSongUris(): List = repository.albumById(id).songs.map {
+ MusicUtil.getSongFileUri(it.id)
+ }
+
+ override fun setColors(color: Int) {
+ super.setColors(color)
+ saveFab.backgroundTintList = ColorStateList.valueOf(color)
+ saveFab.backgroundTintList = ColorStateList.valueOf(color)
+ ColorStateList.valueOf(
+ MaterialValueHelper.getPrimaryTextColor(
+ this,
+ color.isColorLight
+ )
+ ).also {
+ saveFab.iconTint = it
+ saveFab.setTextColor(it)
+ }
+ }
+
+
+ override val editorImage: ImageView
+ get() = binding.editorImage
+
+ companion object {
+
+ val TAG: String = AlbumTagEditorActivity::class.java.simpleName
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/SongTagEditorActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/SongTagEditorActivity.kt
new file mode 100755
index 000000000..93dd8a44f
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/SongTagEditorActivity.kt
@@ -0,0 +1,214 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.activities.tageditor
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.content.res.ColorStateList
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.widget.ImageView
+import android.widget.Toast
+import androidx.core.widget.doAfterTextChanged
+import code.name.monkey.appthemehelper.util.MaterialValueHelper
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.databinding.ActivitySongTagEditorBinding
+import code.name.monkey.retromusic.extensions.*
+import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
+import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
+import code.name.monkey.retromusic.model.ArtworkInfo
+import code.name.monkey.retromusic.repository.SongRepository
+import code.name.monkey.retromusic.util.ImageUtil
+import code.name.monkey.retromusic.util.MusicUtil
+import code.name.monkey.retromusic.util.RetroColorUtil
+import code.name.monkey.retromusic.util.logD
+import com.bumptech.glide.Glide
+import com.bumptech.glide.load.engine.DiskCacheStrategy
+import com.bumptech.glide.request.target.ImageViewTarget
+import com.bumptech.glide.request.transition.Transition
+import com.google.android.material.shape.MaterialShapeDrawable
+import org.jaudiotagger.tag.FieldKey
+import org.koin.android.ext.android.inject
+import java.util.*
+
+class SongTagEditorActivity : AbsTagEditorActivity() {
+
+ override val bindingInflater: (LayoutInflater) -> ActivitySongTagEditorBinding =
+ ActivitySongTagEditorBinding::inflate
+
+
+ private val songRepository by inject()
+
+ private var albumArtBitmap: Bitmap? = null
+ private var deleteAlbumArt: Boolean = false
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setUpViews()
+ setSupportActionBar(binding.toolbar)
+ binding.appBarLayout?.statusBarForeground =
+ MaterialShapeDrawable.createWithElevationOverlay(this)
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ private fun setUpViews() {
+ fillViewsWithFileTags()
+ binding.songTextContainer.setTint(false)
+ binding.composerContainer.setTint(false)
+ binding.albumTextContainer.setTint(false)
+ binding.artistContainer.setTint(false)
+ binding.albumArtistContainer.setTint(false)
+ binding.yearContainer.setTint(false)
+ binding.genreContainer.setTint(false)
+ binding.trackNumberContainer.setTint(false)
+ binding.discNumberContainer.setTint(false)
+ binding.lyricsContainer.setTint(false)
+
+ binding.songText.appHandleColor().doAfterTextChanged { dataChanged() }
+ binding.albumText.appHandleColor().doAfterTextChanged { dataChanged() }
+ binding.albumArtistText.appHandleColor().doAfterTextChanged { dataChanged() }
+ binding.artistText.appHandleColor().doAfterTextChanged { dataChanged() }
+ binding.genreText.appHandleColor().doAfterTextChanged { dataChanged() }
+ binding.yearText.appHandleColor().doAfterTextChanged { dataChanged() }
+ binding.trackNumberText.appHandleColor().doAfterTextChanged { dataChanged() }
+ binding.discNumberText.appHandleColor().doAfterTextChanged { dataChanged() }
+ binding.lyricsText.appHandleColor().doAfterTextChanged { dataChanged() }
+ binding.songComposerText.appHandleColor().doAfterTextChanged { dataChanged() }
+ }
+
+ private fun fillViewsWithFileTags() {
+ binding.songText.setText(songTitle)
+ binding.albumArtistText.setText(albumArtist)
+ binding.albumText.setText(albumTitle)
+ binding.artistText.setText(artistName)
+ binding.genreText.setText(genreName)
+ binding.yearText.setText(songYear)
+ binding.trackNumberText.setText(trackNumber)
+ binding.discNumberText.setText(discNumber)
+ binding.lyricsText.setText(lyrics)
+ binding.songComposerText.setText(composer)
+ logD(songTitle + songYear)
+ }
+
+ override fun loadCurrentImage() {
+ val bitmap = albumArt
+ setImageBitmap(
+ bitmap,
+ RetroColorUtil.getColor(
+ RetroColorUtil.generatePalette(bitmap),
+ defaultFooterColor()
+ )
+ )
+ deleteAlbumArt = false
+ }
+
+ override fun searchImageOnWeb() {
+ searchWebFor(binding.songText.text.toString(), binding.artistText.text.toString())
+ }
+
+ override fun deleteImage() {
+ setImageBitmap(
+ BitmapFactory.decodeResource(resources, R.drawable.default_audio_art),
+ defaultFooterColor()
+ )
+ deleteAlbumArt = true
+ dataChanged()
+ }
+
+ override fun setColors(color: Int) {
+ super.setColors(color)
+ saveFab.backgroundTintList = ColorStateList.valueOf(color)
+ ColorStateList.valueOf(
+ MaterialValueHelper.getPrimaryTextColor(
+ this,
+ color.isColorLight
+ )
+ ).also {
+ saveFab.iconTint = it
+ saveFab.setTextColor(it)
+ }
+ }
+
+ override fun save() {
+ val fieldKeyValueMap = EnumMap(FieldKey::class.java)
+ fieldKeyValueMap[FieldKey.TITLE] = binding.songText.text.toString()
+ fieldKeyValueMap[FieldKey.ALBUM] = binding.albumText.text.toString()
+ fieldKeyValueMap[FieldKey.ARTIST] = binding.artistText.text.toString()
+ fieldKeyValueMap[FieldKey.GENRE] = binding.genreText.text.toString()
+ fieldKeyValueMap[FieldKey.YEAR] = binding.yearText.text.toString()
+ fieldKeyValueMap[FieldKey.TRACK] = binding.trackNumberText.text.toString()
+ fieldKeyValueMap[FieldKey.DISC_NO] = binding.discNumberText.text.toString()
+ fieldKeyValueMap[FieldKey.LYRICS] = binding.lyricsText.text.toString()
+ fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = binding.albumArtistText.text.toString()
+ fieldKeyValueMap[FieldKey.COMPOSER] = binding.songComposerText.text.toString()
+ writeValuesToFiles(
+ fieldKeyValueMap, when {
+ deleteAlbumArt -> ArtworkInfo(id, null)
+ albumArtBitmap == null -> null
+ else -> ArtworkInfo(id, albumArtBitmap!!)
+ }
+ )
+ }
+
+ override fun getSongPaths(): List = listOf(songRepository.song(id).data)
+
+ override fun getSongUris(): List = listOf(MusicUtil.getSongFileUri(id))
+
+ override fun loadImageFromFile(selectedFile: Uri?) {
+ Glide.with(this@SongTagEditorActivity)
+ .asBitmapPalette()
+ .load(selectedFile)
+ .diskCacheStrategy(DiskCacheStrategy.NONE)
+ .skipMemoryCache(true)
+ .into(object : ImageViewTarget(binding.editorImage) {
+ override fun onResourceReady(
+ resource: BitmapPaletteWrapper,
+ transition: Transition?
+ ) {
+ RetroColorUtil.getColor(resource.palette, Color.TRANSPARENT)
+ albumArtBitmap = resource.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) }
+ setImageBitmap(
+ albumArtBitmap,
+ RetroColorUtil.getColor(
+ resource.palette,
+ defaultFooterColor()
+ )
+ )
+ deleteAlbumArt = false
+ dataChanged()
+ setResult(Activity.RESULT_OK)
+ }
+
+ override fun onLoadFailed(errorDrawable: Drawable?) {
+ super.onLoadFailed(errorDrawable)
+ showToast(R.string.error_load_failed, Toast.LENGTH_LONG)
+ }
+
+ override fun setResource(resource: BitmapPaletteWrapper?) {}
+ })
+ }
+
+ companion object {
+ val TAG: String = SongTagEditorActivity::class.java.simpleName
+ }
+
+ override val editorImage: ImageView
+ get() = binding.editorImage
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/TagWriter.kt b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/TagWriter.kt
new file mode 100644
index 000000000..b9d5f28dd
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/TagWriter.kt
@@ -0,0 +1,204 @@
+package code.name.monkey.retromusic.activities.tageditor
+
+import android.app.Activity
+import android.content.Context
+import android.graphics.Bitmap
+import android.media.MediaScannerConnection
+import android.os.Build
+import android.util.Log
+import androidx.annotation.RequiresApi
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.extensions.showToast
+import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener
+import code.name.monkey.retromusic.model.AudioTagInfo
+import code.name.monkey.retromusic.util.MusicUtil.createAlbumArtFile
+import code.name.monkey.retromusic.util.MusicUtil.deleteAlbumArt
+import code.name.monkey.retromusic.util.MusicUtil.insertAlbumArt
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import org.jaudiotagger.audio.AudioFileIO
+import org.jaudiotagger.audio.exceptions.CannotReadException
+import org.jaudiotagger.audio.exceptions.CannotWriteException
+import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException
+import org.jaudiotagger.audio.exceptions.ReadOnlyFileException
+import org.jaudiotagger.tag.FieldDataInvalidException
+import org.jaudiotagger.tag.TagException
+import org.jaudiotagger.tag.images.AndroidArtwork
+import org.jaudiotagger.tag.images.Artwork
+import java.io.File
+import java.io.IOException
+
+class TagWriter {
+
+ companion object {
+
+ suspend fun scan(context: Context, toBeScanned: List?) {
+ if (toBeScanned.isNullOrEmpty()) {
+ Log.i("scan", "scan: Empty")
+ context.showToast("Scan file from folder")
+ return
+ }
+ MediaScannerConnection.scanFile(
+ context,
+ toBeScanned.toTypedArray(),
+ null,
+ withContext(Dispatchers.Main) {
+ if (context is Activity) UpdateToastMediaScannerCompletionListener(
+ context, toBeScanned
+ ) else null
+ }
+ )
+ }
+
+ suspend fun writeTagsToFiles(context: Context, info: AudioTagInfo) {
+ withContext(Dispatchers.IO) {
+ var artwork: Artwork? = null
+ var albumArtFile: File? = null
+ if (info.artworkInfo?.artwork != null) {
+ try {
+ albumArtFile = createAlbumArtFile(context).canonicalFile
+ info.artworkInfo.artwork.compress(
+ Bitmap.CompressFormat.JPEG,
+ 100,
+ albumArtFile.outputStream()
+ )
+ artwork = AndroidArtwork.createArtworkFromFile(albumArtFile)
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+ }
+ var wroteArtwork = false
+ var deletedArtwork = false
+ for (filePath in info.filePaths!!) {
+ try {
+ val audioFile = AudioFileIO.read(File(filePath))
+ val tag = audioFile.tagOrCreateAndSetDefault
+ if (info.fieldKeyValueMap != null) {
+ for ((key, value) in info.fieldKeyValueMap) {
+ try {
+ tag.setField(key, value)
+ } catch (e: FieldDataInvalidException) {
+ withContext(Dispatchers.Main) {
+ context.showToast(R.string.could_not_write_tags_to_file)
+ }
+ return@withContext listOf()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ }
+ if (info.artworkInfo != null) {
+ if (info.artworkInfo.artwork == null) {
+ tag.deleteArtworkField()
+ deletedArtwork = true
+ } else if (artwork != null) {
+ tag.deleteArtworkField()
+ tag.setField(artwork)
+ wroteArtwork = true
+ }
+ }
+ audioFile.commit()
+ } catch (e: CannotReadException) {
+ e.printStackTrace()
+ } catch (e: IOException) {
+ e.printStackTrace()
+ } catch (e: CannotWriteException) {
+ e.printStackTrace()
+ } catch (e: TagException) {
+ e.printStackTrace()
+ } catch (e: ReadOnlyFileException) {
+ e.printStackTrace()
+ } catch (e: InvalidAudioFrameException) {
+ e.printStackTrace()
+ }
+ }
+ if (wroteArtwork) {
+ insertAlbumArt(context, info.artworkInfo!!.albumId, albumArtFile!!.path)
+ } else if (deletedArtwork) {
+ deleteAlbumArt(context, info.artworkInfo!!.albumId)
+ }
+ scan(context, info.filePaths)
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.R)
+ suspend fun writeTagsToFilesR(context: Context, info: AudioTagInfo): List =
+ withContext(Dispatchers.IO) {
+ val cacheFiles = mutableListOf()
+ var artwork: Artwork? = null
+ var albumArtFile: File? = null
+ if (info.artworkInfo?.artwork != null) {
+ try {
+ albumArtFile = createAlbumArtFile(context).canonicalFile
+ info.artworkInfo.artwork.compress(
+ Bitmap.CompressFormat.JPEG,
+ 100,
+ albumArtFile.outputStream()
+ )
+ artwork = AndroidArtwork.createArtworkFromFile(albumArtFile)
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+ }
+ var wroteArtwork = false
+ var deletedArtwork = false
+ for (filePath in info.filePaths!!) {
+ try {
+ val originFile = File(filePath)
+ val cacheFile = File(context.cacheDir, originFile.name)
+ cacheFiles.add(cacheFile)
+ originFile.inputStream().use { input ->
+ cacheFile.outputStream().use { output ->
+ input.copyTo(output)
+ }
+ }
+ val audioFile = AudioFileIO.read(cacheFile)
+ val tag = audioFile.tagOrCreateAndSetDefault
+ if (info.fieldKeyValueMap != null) {
+ for ((key, value) in info.fieldKeyValueMap) {
+ try {
+ tag.setField(key, value)
+ } catch (e: FieldDataInvalidException) {
+ withContext(Dispatchers.Main) {
+ context.showToast(R.string.could_not_write_tags_to_file)
+ }
+ return@withContext listOf()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ }
+ if (info.artworkInfo != null) {
+ if (info.artworkInfo.artwork == null) {
+ tag.deleteArtworkField()
+ deletedArtwork = true
+ } else if (artwork != null) {
+ tag.deleteArtworkField()
+ tag.setField(artwork)
+ wroteArtwork = true
+ }
+ }
+ audioFile.commit()
+ } catch (e: CannotReadException) {
+ e.printStackTrace()
+ } catch (e: IOException) {
+ e.printStackTrace()
+ } catch (e: CannotWriteException) {
+ e.printStackTrace()
+ } catch (e: TagException) {
+ e.printStackTrace()
+ } catch (e: ReadOnlyFileException) {
+ e.printStackTrace()
+ } catch (e: InvalidAudioFrameException) {
+ e.printStackTrace()
+ }
+ }
+ if (wroteArtwork) {
+ insertAlbumArt(context, info.artworkInfo!!.albumId, albumArtFile!!.path)
+ } else if (deletedArtwork) {
+ deleteAlbumArt(context, info.artworkInfo!!.albumId)
+ }
+ cacheFiles
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/CategoryInfoAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/CategoryInfoAdapter.kt
new file mode 100644
index 000000000..dd5cda1d3
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/CategoryInfoAdapter.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2019 Hemanth Savarala.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by
+ * the Free Software Foundation either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ */
+package code.name.monkey.retromusic.adapter
+
+import android.annotation.SuppressLint
+import android.content.res.ColorStateList
+import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.ItemTouchHelper
+import androidx.recyclerview.widget.RecyclerView
+import code.name.monkey.appthemehelper.ThemeStore.Companion.accentColor
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.databinding.PreferenceDialogLibraryCategoriesListitemBinding
+import code.name.monkey.retromusic.extensions.showToast
+import code.name.monkey.retromusic.model.CategoryInfo
+import code.name.monkey.retromusic.util.PreferenceUtil
+import code.name.monkey.retromusic.util.SwipeAndDragHelper
+import code.name.monkey.retromusic.util.SwipeAndDragHelper.ActionCompletionContract
+
+class CategoryInfoAdapter : RecyclerView.Adapter(),
+ ActionCompletionContract {
+ var categoryInfos: MutableList =
+ PreferenceUtil.libraryCategory.toMutableList()
+ @SuppressLint("NotifyDataSetChanged")
+ set(value) {
+ field = value
+ notifyDataSetChanged()
+ }
+ private val touchHelper: ItemTouchHelper
+ fun attachToRecyclerView(recyclerView: RecyclerView?) {
+ touchHelper.attachToRecyclerView(recyclerView)
+ }
+
+ override fun getItemCount(): Int {
+ return categoryInfos.size
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val categoryInfo = categoryInfos[position]
+ holder.binding.checkbox.isChecked = categoryInfo.visible
+ holder.binding.title.text =
+ holder.binding.title.resources.getString(categoryInfo.category.stringRes)
+ holder.itemView.setOnClickListener {
+ if (!(categoryInfo.visible && isLastCheckedCategory(categoryInfo))) {
+ categoryInfo.visible = !categoryInfo.visible
+ holder.binding.checkbox.isChecked = categoryInfo.visible
+ } else {
+ holder.itemView.context.showToast(R.string.you_have_to_select_at_least_one_category)
+ }
+ }
+ holder.binding.dragView.setOnTouchListener { _: View?, event: MotionEvent ->
+ if (event.actionMasked == MotionEvent.ACTION_DOWN) {
+ touchHelper.startDrag(holder)
+ }
+ false
+ }
+ }
+
+ override fun onCreateViewHolder(
+ parent: ViewGroup, viewType: Int
+ ): ViewHolder {
+ return ViewHolder(
+ PreferenceDialogLibraryCategoriesListitemBinding.inflate(
+ LayoutInflater.from(
+ parent.context
+ ), parent, false
+ )
+ )
+ }
+
+ override fun onViewMoved(oldPosition: Int, newPosition: Int) {
+ val categoryInfo = categoryInfos[oldPosition]
+ categoryInfos.removeAt(oldPosition)
+ categoryInfos.add(newPosition, categoryInfo)
+ notifyItemMoved(oldPosition, newPosition)
+ }
+
+ private fun isLastCheckedCategory(categoryInfo: CategoryInfo): Boolean {
+ if (categoryInfo.visible) {
+ for (c in categoryInfos) {
+ if (c !== categoryInfo && c.visible) {
+ return false
+ }
+ }
+ }
+ return true
+ }
+
+ class ViewHolder(val binding: PreferenceDialogLibraryCategoriesListitemBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+
+ init {
+ binding.checkbox.buttonTintList =
+ ColorStateList.valueOf(accentColor(binding.checkbox.context))
+ }
+ }
+
+ init {
+ val swipeAndDragHelper = SwipeAndDragHelper(this)
+ touchHelper = ItemTouchHelper(swipeAndDragHelper)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/GenreAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/GenreAdapter.kt
new file mode 100644
index 000000000..b139a0af8
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/GenreAdapter.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.adapter
+
+import android.annotation.SuppressLint
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewOutlineProvider
+import androidx.fragment.app.FragmentActivity
+import androidx.recyclerview.widget.RecyclerView
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.databinding.ItemGenreBinding
+import code.name.monkey.retromusic.glide.RetroGlideExtension
+import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
+import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
+import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
+import code.name.monkey.retromusic.interfaces.IGenreClickListener
+import code.name.monkey.retromusic.model.Genre
+import code.name.monkey.retromusic.util.MusicUtil
+import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
+import com.bumptech.glide.Glide
+import java.util.*
+
+/**
+ * @author Hemanth S (h4h13).
+ */
+
+class GenreAdapter(
+ private val activity: FragmentActivity,
+ var dataSet: List,
+ private val listener: IGenreClickListener
+) : RecyclerView.Adapter() {
+
+ init {
+ this.setHasStableIds(true)
+ }
+
+ override fun getItemId(position: Int): Long {
+ return dataSet[position].id
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ return ViewHolder(ItemGenreBinding.inflate(LayoutInflater.from(activity), parent, false))
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val genre = dataSet[position]
+ holder.binding.title.text = genre.name
+ holder.binding.text.text = String.format(
+ Locale.getDefault(),
+ "%d %s",
+ genre.songCount,
+ if (genre.songCount > 1) activity.getString(R.string.songs) else activity.getString(R.string.song)
+ )
+ loadGenreImage(genre, holder)
+ }
+
+ private fun loadGenreImage(genre: Genre, holder: GenreAdapter.ViewHolder) {
+ val genreSong = MusicUtil.songByGenre(genre.id)
+ Glide.with(activity)
+ .asBitmapPalette()
+ .songCoverOptions(genreSong)
+ .load(RetroGlideExtension.getSongModel(genreSong))
+ .into(object : RetroMusicColoredTarget(holder.binding.image) {
+ override fun onColorReady(colors: MediaNotificationProcessor) {
+ setColors(holder, colors)
+ }
+ })
+ // Just for a bit of shadow around image
+ holder.binding.image.outlineProvider = ViewOutlineProvider.BOUNDS
+ }
+
+ private fun setColors(holder: ViewHolder, color: MediaNotificationProcessor) {
+ holder.binding.imageContainerCard.setCardBackgroundColor(color.backgroundColor)
+ holder.binding.title.setTextColor(color.primaryTextColor)
+ holder.binding.text.setTextColor(color.secondaryTextColor)
+ }
+
+ override fun getItemCount(): Int {
+ return dataSet.size
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
+ fun swapDataSet(list: List) {
+ dataSet = list
+ notifyDataSetChanged()
+ }
+
+ inner class ViewHolder(val binding: ItemGenreBinding) : RecyclerView.ViewHolder(binding.root),
+ View.OnClickListener {
+ override fun onClick(v: View?) {
+ listener.onClickGenre(dataSet[layoutPosition], itemView)
+ }
+
+ init {
+ itemView.setOnClickListener(this)
+ }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/HomeAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/HomeAdapter.kt
new file mode 100644
index 000000000..e3f809b5f
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/HomeAdapter.kt
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.adapter
+
+import android.annotation.SuppressLint
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.widget.AppCompatTextView
+import androidx.core.os.bundleOf
+import androidx.fragment.app.findFragment
+import androidx.navigation.findNavController
+import androidx.navigation.fragment.FragmentNavigatorExtras
+import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import code.name.monkey.retromusic.*
+import code.name.monkey.retromusic.adapter.album.AlbumAdapter
+import code.name.monkey.retromusic.adapter.artist.ArtistAdapter
+import code.name.monkey.retromusic.adapter.song.SongAdapter
+import code.name.monkey.retromusic.fragments.home.HomeFragment
+import code.name.monkey.retromusic.interfaces.IAlbumClickListener
+import code.name.monkey.retromusic.interfaces.IArtistClickListener
+import code.name.monkey.retromusic.model.Album
+import code.name.monkey.retromusic.model.Artist
+import code.name.monkey.retromusic.model.Home
+import code.name.monkey.retromusic.model.Song
+import code.name.monkey.retromusic.util.PreferenceUtil
+
+class HomeAdapter(private val activity: AppCompatActivity) :
+ RecyclerView.Adapter(), IArtistClickListener, IAlbumClickListener {
+
+ private var list = listOf()
+
+ override fun getItemViewType(position: Int): Int {
+ return list[position].homeSection
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+ val layout =
+ LayoutInflater.from(activity).inflate(R.layout.section_recycler_view, parent, false)
+ return when (viewType) {
+ RECENT_ARTISTS, TOP_ARTISTS -> ArtistViewHolder(layout)
+ FAVOURITES -> PlaylistViewHolder(layout)
+ TOP_ALBUMS, RECENT_ALBUMS -> AlbumViewHolder(layout)
+ else -> {
+ ArtistViewHolder(layout)
+ }
+ }
+ }
+
+ override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+ val home = list[position]
+ when (getItemViewType(position)) {
+ RECENT_ALBUMS -> {
+ val viewHolder = holder as AlbumViewHolder
+ viewHolder.bindView(home)
+ viewHolder.clickableArea.setOnClickListener {
+ it.findFragment().setSharedAxisXTransitions()
+ activity.findNavController(R.id.fragment_container).navigate(
+ R.id.detailListFragment,
+ bundleOf("type" to RECENT_ALBUMS)
+ )
+ }
+ }
+ TOP_ALBUMS -> {
+ val viewHolder = holder as AlbumViewHolder
+ viewHolder.bindView(home)
+ viewHolder.clickableArea.setOnClickListener {
+ it.findFragment().setSharedAxisXTransitions()
+ activity.findNavController(R.id.fragment_container).navigate(
+ R.id.detailListFragment,
+ bundleOf("type" to TOP_ALBUMS)
+ )
+ }
+ }
+ RECENT_ARTISTS -> {
+ val viewHolder = holder as ArtistViewHolder
+ viewHolder.bindView(home)
+ viewHolder.clickableArea.setOnClickListener {
+ it.findFragment().setSharedAxisXTransitions()
+ activity.findNavController(R.id.fragment_container).navigate(
+ R.id.detailListFragment,
+ bundleOf("type" to RECENT_ARTISTS)
+ )
+ }
+ }
+ TOP_ARTISTS -> {
+ val viewHolder = holder as ArtistViewHolder
+ viewHolder.bindView(home)
+ viewHolder.clickableArea.setOnClickListener {
+ it.findFragment().setSharedAxisXTransitions()
+ activity.findNavController(R.id.fragment_container).navigate(
+ R.id.detailListFragment,
+ bundleOf("type" to TOP_ARTISTS)
+ )
+ }
+ }
+ FAVOURITES -> {
+ val viewHolder = holder as PlaylistViewHolder
+ viewHolder.bindView(home)
+ viewHolder.clickableArea.setOnClickListener {
+ it.findFragment().setSharedAxisXTransitions()
+ activity.findNavController(R.id.fragment_container).navigate(
+ R.id.detailListFragment,
+ bundleOf("type" to FAVOURITES)
+ )
+ }
+ }
+ }
+ }
+
+ override fun getItemCount(): Int {
+ return list.size
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
+ fun swapData(sections: List) {
+ list = sections
+ notifyDataSetChanged()
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private inner class AlbumViewHolder(view: View) : AbsHomeViewItem(view) {
+ fun bindView(home: Home) {
+ title.setText(home.titleRes)
+ recyclerView.apply {
+ adapter = albumAdapter(home.arrayList as List)
+ layoutManager = gridLayoutManager()
+ }
+ }
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private inner class ArtistViewHolder(view: View) : AbsHomeViewItem(view) {
+ fun bindView(home: Home) {
+ title.setText(home.titleRes)
+ recyclerView.apply {
+ layoutManager = linearLayoutManager()
+ adapter = artistsAdapter(home.arrayList as List)
+ }
+ }
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private inner class PlaylistViewHolder(view: View) : AbsHomeViewItem(view) {
+ fun bindView(home: Home) {
+ title.setText(home.titleRes)
+ recyclerView.apply {
+ val songAdapter = SongAdapter(
+ activity,
+ home.arrayList as MutableList,
+ R.layout.item_favourite_card
+ )
+ layoutManager = linearLayoutManager()
+ adapter = songAdapter
+ }
+ }
+ }
+
+ open class AbsHomeViewItem(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val recyclerView: RecyclerView = itemView.findViewById(R.id.recyclerView)
+ val title: AppCompatTextView = itemView.findViewById(R.id.title)
+ val clickableArea: ViewGroup = itemView.findViewById(R.id.clickable_area)
+ }
+
+ private fun artistsAdapter(artists: List) =
+ ArtistAdapter(activity, artists, PreferenceUtil.homeArtistGridStyle, this)
+
+ private fun albumAdapter(albums: List) =
+ AlbumAdapter(activity, albums, PreferenceUtil.homeAlbumGridStyle, this)
+
+ private fun gridLayoutManager() =
+ GridLayoutManager(activity, 1, GridLayoutManager.HORIZONTAL, false)
+
+ private fun linearLayoutManager() =
+ LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false)
+
+ override fun onArtist(artistId: Long, view: View) {
+ activity.findNavController(R.id.fragment_container).navigate(
+ R.id.artistDetailsFragment,
+ bundleOf(EXTRA_ARTIST_ID to artistId),
+ null,
+ FragmentNavigatorExtras(
+ view to artistId.toString()
+ )
+ )
+ }
+
+ override fun onAlbumClick(albumId: Long, view: View) {
+ activity.findNavController(R.id.fragment_container).navigate(
+ R.id.albumDetailsFragment,
+ bundleOf(EXTRA_ALBUM_ID to albumId),
+ null,
+ FragmentNavigatorExtras(
+ view to albumId.toString()
+ )
+ )
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/SearchAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/SearchAdapter.kt
new file mode 100644
index 000000000..22a60bd95
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/SearchAdapter.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.adapter
+
+import android.annotation.SuppressLint
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.os.bundleOf
+import androidx.core.view.isGone
+import androidx.core.view.isInvisible
+import androidx.core.view.isVisible
+import androidx.fragment.app.FragmentActivity
+import androidx.navigation.findNavController
+import androidx.recyclerview.widget.RecyclerView
+import code.name.monkey.appthemehelper.ThemeStore
+import code.name.monkey.retromusic.*
+import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
+import code.name.monkey.retromusic.db.PlaylistWithSongs
+import code.name.monkey.retromusic.glide.RetroGlideExtension
+import code.name.monkey.retromusic.glide.RetroGlideExtension.albumCoverOptions
+import code.name.monkey.retromusic.glide.RetroGlideExtension.artistImageOptions
+import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
+import code.name.monkey.retromusic.helper.MusicPlayerRemote
+import code.name.monkey.retromusic.helper.menu.SongMenuHelper
+import code.name.monkey.retromusic.model.Album
+import code.name.monkey.retromusic.model.Artist
+import code.name.monkey.retromusic.model.Genre
+import code.name.monkey.retromusic.model.Song
+import code.name.monkey.retromusic.util.MusicUtil
+import com.bumptech.glide.Glide
+import java.util.*
+
+class SearchAdapter(
+ private val activity: FragmentActivity,
+ private var dataSet: List
+) : RecyclerView.Adapter() {
+
+ @SuppressLint("NotifyDataSetChanged")
+ fun swapDataSet(dataSet: List) {
+ this.dataSet = dataSet
+ notifyDataSetChanged()
+ }
+
+ override fun getItemViewType(position: Int): Int {
+ if (dataSet[position] is Album) return ALBUM
+ if (dataSet[position] is Artist) return if ((dataSet[position] as Artist).isAlbumArtist) ALBUM_ARTIST else ARTIST
+ if (dataSet[position] is Genre) return GENRE
+ if (dataSet[position] is PlaylistWithSongs) return PLAYLIST
+ return if (dataSet[position] is Song) SONG else HEADER
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ return when (viewType) {
+ HEADER -> ViewHolder(
+ LayoutInflater.from(activity).inflate(
+ R.layout.sub_header,
+ parent,
+ false
+ ), viewType
+ )
+
+ ALBUM, ARTIST, ALBUM_ARTIST -> ViewHolder(
+ LayoutInflater.from(activity).inflate(
+ R.layout.item_list_big,
+ parent,
+ false
+ ), viewType
+ )
+
+ else -> ViewHolder(
+ LayoutInflater.from(activity).inflate(R.layout.item_list, parent, false),
+ viewType
+ )
+ }
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ when (getItemViewType(position)) {
+ ALBUM -> {
+ holder.imageTextContainer?.isVisible = true
+ val album = dataSet[position] as Album
+ holder.title?.text = album.title
+ holder.text?.text = album.artistName
+ Glide.with(activity).asDrawable().albumCoverOptions(album.safeGetFirstSong())
+ .load(RetroGlideExtension.getSongModel(album.safeGetFirstSong()))
+ .into(holder.image!!)
+ }
+
+ ARTIST -> {
+ holder.imageTextContainer?.isVisible = true
+ val artist = dataSet[position] as Artist
+ holder.title?.text = artist.name
+ holder.text?.text = MusicUtil.getArtistInfoString(activity, artist)
+ Glide.with(activity).asDrawable().artistImageOptions(artist).load(
+ RetroGlideExtension.getArtistModel(artist)
+ ).into(holder.image!!)
+ }
+
+ SONG -> {
+ holder.imageTextContainer?.isVisible = true
+ val song = dataSet[position] as Song
+ holder.title?.text = song.title
+ holder.text?.text = song.albumName
+ Glide.with(activity).asDrawable().songCoverOptions(song)
+ .load(RetroGlideExtension.getSongModel(song)).into(holder.image!!)
+ }
+
+ GENRE -> {
+ val genre = dataSet[position] as Genre
+ holder.title?.text = genre.name
+ holder.text?.text = String.format(
+ Locale.getDefault(),
+ "%d %s",
+ genre.songCount,
+ if (genre.songCount > 1) activity.getString(R.string.songs) else activity.getString(
+ R.string.song
+ )
+ )
+ }
+
+ PLAYLIST -> {
+ val playlist = dataSet[position] as PlaylistWithSongs
+ holder.title?.text = playlist.playlistEntity.playlistName
+ //holder.text?.text = MusicUtil.playlistInfoString(activity, playlist.songs)
+ }
+
+ ALBUM_ARTIST -> {
+ holder.imageTextContainer?.isVisible = true
+ val artist = dataSet[position] as Artist
+ holder.title?.text = artist.name
+ holder.text?.text = MusicUtil.getArtistInfoString(activity, artist)
+ Glide.with(activity).asDrawable().artistImageOptions(artist).load(
+ RetroGlideExtension.getArtistModel(artist)
+ ).into(holder.image!!)
+ }
+
+ else -> {
+ holder.title?.text = dataSet[position].toString()
+ holder.title?.setTextColor(ThemeStore.accentColor(activity))
+ }
+ }
+ }
+
+ override fun getItemCount(): Int {
+ return dataSet.size
+ }
+
+ inner class ViewHolder(itemView: View, itemViewType: Int) : MediaEntryViewHolder(itemView) {
+ init {
+ itemView.setOnLongClickListener(null)
+ imageTextContainer?.isInvisible = true
+ if (itemViewType == SONG) {
+ imageTextContainer?.isGone = true
+ menu?.isVisible = true
+ menu?.setOnClickListener(object : SongMenuHelper.OnClickSongMenu(activity) {
+ override val song: Song
+ get() = dataSet[layoutPosition] as Song
+ })
+ } else {
+ menu?.isVisible = false
+ }
+
+ when (itemViewType) {
+ ALBUM -> setImageTransitionName(activity.getString(R.string.transition_album_art))
+ ARTIST -> setImageTransitionName(activity.getString(R.string.transition_artist_image))
+ else -> {
+ val container = itemView.findViewById(R.id.imageContainer)
+ container?.isVisible = false
+ }
+ }
+ }
+
+ override fun onClick(v: View?) {
+ val item = dataSet[layoutPosition]
+ when (itemViewType) {
+ ALBUM -> {
+ activity.findNavController(R.id.fragment_container).navigate(
+ R.id.albumDetailsFragment,
+ bundleOf(EXTRA_ALBUM_ID to (item as Album).id)
+ )
+ }
+
+ ARTIST -> {
+ activity.findNavController(R.id.fragment_container).navigate(
+ R.id.artistDetailsFragment,
+ bundleOf(EXTRA_ARTIST_ID to (item as Artist).id)
+ )
+ }
+
+ ALBUM_ARTIST -> {
+ activity.findNavController(R.id.fragment_container).navigate(
+ R.id.albumArtistDetailsFragment,
+ bundleOf(EXTRA_ARTIST_NAME to (item as Artist).name)
+ )
+ }
+
+ GENRE -> {
+ activity.findNavController(R.id.fragment_container).navigate(
+ R.id.genreDetailsFragment,
+ bundleOf(EXTRA_GENRE to (item as Genre))
+ )
+ }
+
+ PLAYLIST -> {
+ activity.findNavController(R.id.fragment_container).navigate(
+ R.id.playlistDetailsFragment,
+ bundleOf(EXTRA_PLAYLIST_ID to (item as PlaylistWithSongs).playlistEntity.playListId)
+ )
+ }
+
+ SONG -> {
+ MusicPlayerRemote.playNext(item as Song)
+ MusicPlayerRemote.playNextSong()
+ }
+ }
+ }
+ }
+
+ companion object {
+ private const val HEADER = 0
+ private const val ALBUM = 1
+ private const val ARTIST = 2
+ private const val SONG = 3
+ private const val GENRE = 4
+ private const val PLAYLIST = 5
+ private const val ALBUM_ARTIST = 6
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/SongFileAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/SongFileAdapter.kt
new file mode 100644
index 000000000..4005c6e6c
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/SongFileAdapter.kt
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.adapter
+
+import android.graphics.PorterDuff
+import android.view.LayoutInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.isVisible
+import code.name.monkey.appthemehelper.util.ATHUtil
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
+import code.name.monkey.retromusic.extensions.getTintedDrawable
+import code.name.monkey.retromusic.glide.RetroGlideExtension
+import code.name.monkey.retromusic.glide.audiocover.AudioFileCover
+import code.name.monkey.retromusic.interfaces.ICallbacks
+import code.name.monkey.retromusic.util.MusicUtil
+import com.bumptech.glide.Glide
+import com.bumptech.glide.load.engine.DiskCacheStrategy
+import com.bumptech.glide.signature.MediaStoreSignature
+import me.zhanghai.android.fastscroll.PopupTextProvider
+import java.io.File
+import java.text.DecimalFormat
+import kotlin.math.log10
+import kotlin.math.pow
+
+class SongFileAdapter(
+ override val activity: AppCompatActivity,
+ private var dataSet: List,
+ private val itemLayoutRes: Int,
+ private val iCallbacks: ICallbacks?
+) : AbsMultiSelectAdapter(
+ activity, R.menu.menu_media_selection
+), PopupTextProvider {
+
+ init {
+ this.setHasStableIds(true)
+ }
+
+ override fun getItemViewType(position: Int): Int {
+ return if (dataSet[position].isDirectory) FOLDER else FILE
+ }
+
+ override fun getItemId(position: Int): Long {
+ return dataSet[position].hashCode().toLong()
+ }
+
+ fun swapDataSet(songFiles: List) {
+ this.dataSet = songFiles
+ notifyDataSetChanged()
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ return ViewHolder(LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false))
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, index: Int) {
+ val file = dataSet[index]
+ holder.itemView.isActivated = isChecked(file)
+ holder.title?.text = getFileTitle(file)
+ if (holder.text != null) {
+ if (holder.itemViewType == FILE) {
+ holder.text?.text = getFileText(file)
+ } else {
+ holder.text?.isVisible = false
+ }
+ }
+
+ if (holder.image != null) {
+ loadFileImage(file, holder)
+ }
+ }
+
+ private fun getFileTitle(file: File): String {
+ return file.name
+ }
+
+ private fun getFileText(file: File): String? {
+ return if (file.isDirectory) null else readableFileSize(file.length())
+ }
+
+ private fun loadFileImage(file: File, holder: ViewHolder) {
+ val iconColor = ATHUtil.resolveColor(activity, androidx.appcompat.R.attr.colorControlNormal)
+ if (file.isDirectory) {
+ holder.image?.let {
+ it.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN)
+ it.setImageResource(R.drawable.ic_folder)
+ }
+ holder.imageTextContainer?.setCardBackgroundColor(
+ ATHUtil.resolveColor(
+ activity,
+ com.google.android.material.R.attr.colorSurface
+ )
+ )
+ } else {
+ val error = activity.getTintedDrawable(R.drawable.ic_audio_file, iconColor)
+ Glide.with(activity)
+ .load(AudioFileCover(file.path))
+ .diskCacheStrategy(DiskCacheStrategy.NONE)
+ .error(error)
+ .placeholder(error)
+ .transition(RetroGlideExtension.getDefaultTransition())
+ .signature(MediaStoreSignature("", file.lastModified(), 0))
+ .into(holder.image!!)
+ }
+ }
+
+ override fun getItemCount(): Int {
+ return dataSet.size
+ }
+
+ override fun getIdentifier(position: Int): File {
+ return dataSet[position]
+ }
+
+ override fun getName(model: File): String {
+ return getFileTitle(model)
+ }
+
+ override fun onMultipleItemAction(menuItem: MenuItem, selection: List) {
+ if (iCallbacks == null) return
+ iCallbacks.onMultipleItemAction(menuItem, selection as ArrayList)
+ }
+
+ override fun getPopupText(position: Int): String {
+ return if (position >= dataSet.lastIndex) "" else getSectionName(position)
+ }
+
+ private fun getSectionName(position: Int): String {
+ return MusicUtil.getSectionName(dataSet[position].name)
+ }
+
+ inner class ViewHolder(itemView: View) : code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder(itemView) {
+
+ init {
+ if (menu != null && iCallbacks != null) {
+ menu?.setOnClickListener { v ->
+ val position = layoutPosition
+ if (isPositionInRange(position)) {
+ iCallbacks.onFileMenuClicked(dataSet[position], v)
+ }
+ }
+ }
+ if (imageTextContainer != null) {
+ imageTextContainer?.cardElevation = 0f
+ }
+ }
+
+ override fun onClick(v: View?) {
+ val position = layoutPosition
+ if (isPositionInRange(position)) {
+ if (isInQuickSelectMode) {
+ toggleChecked(position)
+ } else {
+ iCallbacks?.onFileSelected(dataSet[position])
+ }
+ }
+ }
+
+ override fun onLongClick(v: View?): Boolean {
+ val position = layoutPosition
+ return isPositionInRange(position) && toggleChecked(position)
+ }
+
+ private fun isPositionInRange(position: Int): Boolean {
+ return position >= 0 && position < dataSet.size
+ }
+ }
+
+ companion object {
+
+ private const val FILE = 0
+ private const val FOLDER = 1
+
+ fun readableFileSize(size: Long): String {
+ if (size <= 0) return "$size B"
+ val units = arrayOf("B", "KB", "MB", "GB", "TB")
+ val digitGroups = (log10(size.toDouble()) / log10(1024.0)).toInt()
+ return DecimalFormat("#,##0.##").format(size / 1024.0.pow(digitGroups.toDouble())) + " " + units[digitGroups]
+ }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/StorageAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/StorageAdapter.kt
new file mode 100644
index 000000000..98c2c632f
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/StorageAdapter.kt
@@ -0,0 +1,55 @@
+package code.name.monkey.retromusic.adapter
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import code.name.monkey.retromusic.R
+import java.io.File
+
+class StorageAdapter(
+ val storageList: List,
+ val storageClickListener: StorageClickListener
+) :
+ RecyclerView.Adapter() {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ return ViewHolder(
+ LayoutInflater.from(parent.context).inflate(
+ R.layout.item_storage,
+ parent,
+ false
+ )
+ )
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ holder.bindData(storageList[position])
+ }
+
+ override fun getItemCount(): Int {
+ return storageList.size
+ }
+
+ inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val title: TextView = itemView.findViewById(R.id.title)
+
+ fun bindData(storage: Storage) {
+ title.text = storage.title
+ }
+
+ init {
+ itemView.setOnClickListener { storageClickListener.onStorageClicked(storageList[bindingAdapterPosition]) }
+ }
+ }
+}
+
+interface StorageClickListener {
+ fun onStorageClicked(storage: Storage)
+}
+
+class Storage {
+ lateinit var title: String
+ lateinit var file: File
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumAdapter.kt
new file mode 100644
index 000000000..160af7e47
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumAdapter.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.adapter.album
+
+import android.content.res.ColorStateList
+import android.view.LayoutInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.view.isVisible
+import androidx.fragment.app.FragmentActivity
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
+import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
+import code.name.monkey.retromusic.glide.RetroGlideExtension
+import code.name.monkey.retromusic.glide.RetroGlideExtension.albumCoverOptions
+import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
+import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
+import code.name.monkey.retromusic.helper.SortOrder
+import code.name.monkey.retromusic.helper.menu.SongsMenuHelper
+import code.name.monkey.retromusic.interfaces.IAlbumClickListener
+import code.name.monkey.retromusic.model.Album
+import code.name.monkey.retromusic.model.Song
+import code.name.monkey.retromusic.util.MusicUtil
+import code.name.monkey.retromusic.util.PreferenceUtil
+import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
+import com.bumptech.glide.Glide
+import me.zhanghai.android.fastscroll.PopupTextProvider
+
+open class AlbumAdapter(
+ override val activity: FragmentActivity,
+ var dataSet: List,
+ var itemLayoutRes: Int,
+ val listener: IAlbumClickListener?
+) : AbsMultiSelectAdapter(
+ activity,
+ R.menu.menu_media_selection
+), PopupTextProvider {
+
+ init {
+ this.setHasStableIds(true)
+ }
+
+ fun swapDataSet(dataSet: List) {
+ this.dataSet = dataSet
+ notifyDataSetChanged()
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val view = LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false)
+ return createViewHolder(view, viewType)
+ }
+
+ protected open fun createViewHolder(view: View, viewType: Int): ViewHolder {
+ return ViewHolder(view)
+ }
+
+ private fun getAlbumTitle(album: Album): String {
+ return album.title
+ }
+
+ protected open fun getAlbumText(album: Album): String? {
+ return album.albumArtist.let {
+ if (it.isNullOrEmpty()) {
+ album.artistName
+ } else {
+ it
+ }
+ }
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val album = dataSet[position]
+ val isChecked = isChecked(album)
+ holder.itemView.isActivated = isChecked
+ holder.title?.text = getAlbumTitle(album)
+ holder.text?.text = getAlbumText(album)
+ // Check if imageContainer exists so we can have a smooth transition without
+ // CardView clipping, if it doesn't exist in current layout set transition name to image instead.
+ if (holder.imageContainer != null) {
+ holder.imageContainer?.transitionName = album.id.toString()
+ } else {
+ holder.image?.transitionName = album.id.toString()
+ }
+ loadAlbumCover(album, holder)
+ }
+
+ protected open fun setColors(color: MediaNotificationProcessor, holder: ViewHolder) {
+ if (holder.paletteColorContainer != null) {
+ holder.title?.setTextColor(color.primaryTextColor)
+ holder.text?.setTextColor(color.secondaryTextColor)
+ holder.paletteColorContainer?.setBackgroundColor(color.backgroundColor)
+ }
+ holder.mask?.backgroundTintList = ColorStateList.valueOf(color.primaryTextColor)
+ holder.imageContainerCard?.setCardBackgroundColor(color.backgroundColor)
+ }
+
+ protected open fun loadAlbumCover(album: Album, holder: ViewHolder) {
+ if (holder.image == null) {
+ return
+ }
+ val song = album.safeGetFirstSong()
+ Glide.with(activity)
+ .asBitmapPalette()
+ .albumCoverOptions(song)
+ //.checkIgnoreMediaStore()
+ .load(RetroGlideExtension.getSongModel(song))
+ .into(object : RetroMusicColoredTarget(holder.image!!) {
+ override fun onColorReady(colors: MediaNotificationProcessor) {
+ setColors(colors, holder)
+ }
+ })
+ }
+
+ override fun getItemCount(): Int {
+ return dataSet.size
+ }
+
+ override fun getItemId(position: Int): Long {
+ return dataSet[position].id
+ }
+
+ override fun getIdentifier(position: Int): Album? {
+ return dataSet[position]
+ }
+
+ override fun getName(model: Album): String {
+ return model.title
+ }
+
+ override fun onMultipleItemAction(
+ menuItem: MenuItem,
+ selection: List
+ ) {
+ SongsMenuHelper.handleMenuClick(activity, getSongList(selection), menuItem.itemId)
+ }
+
+ private fun getSongList(albums: List): List {
+ val songs = ArrayList()
+ for (album in albums) {
+ songs.addAll(album.songs)
+ }
+ return songs
+ }
+
+ override fun getPopupText(position: Int): String {
+ return getSectionName(position)
+ }
+
+ private fun getSectionName(position: Int): String {
+ var sectionName: String? = null
+ when (PreferenceUtil.albumSortOrder) {
+ SortOrder.AlbumSortOrder.ALBUM_A_Z, SortOrder.AlbumSortOrder.ALBUM_Z_A -> sectionName =
+ dataSet[position].title
+
+ SortOrder.AlbumSortOrder.ALBUM_ARTIST -> sectionName = dataSet[position].albumArtist
+ SortOrder.AlbumSortOrder.ALBUM_YEAR -> return MusicUtil.getYearString(
+ dataSet[position].year
+ )
+ }
+ return MusicUtil.getSectionName(sectionName)
+ }
+
+ inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
+
+ init {
+ menu?.isVisible = false
+ }
+
+ override fun onClick(v: View?) {
+ super.onClick(v)
+ if (isInQuickSelectMode) {
+ toggleChecked(layoutPosition)
+ } else {
+ image?.let {
+ listener?.onAlbumClick(dataSet[layoutPosition].id, imageContainer ?: it)
+ }
+ }
+ }
+
+ override fun onLongClick(v: View?): Boolean {
+ return toggleChecked(layoutPosition)
+ }
+ }
+
+ companion object {
+ val TAG: String = AlbumAdapter::class.java.simpleName
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumCoverPagerAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumCoverPagerAdapter.kt
new file mode 100644
index 000000000..5a0a7d3cd
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumCoverPagerAdapter.kt
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.adapter.album
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import androidx.core.os.BundleCompat
+import androidx.core.os.bundleOf
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
+import androidx.lifecycle.lifecycleScope
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.activities.MainActivity
+import code.name.monkey.retromusic.fragments.AlbumCoverStyle
+import code.name.monkey.retromusic.fragments.NowPlayingScreen.*
+import code.name.monkey.retromusic.fragments.base.goToLyrics
+import code.name.monkey.retromusic.glide.RetroGlideExtension
+import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
+import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
+import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
+import code.name.monkey.retromusic.misc.CustomFragmentStatePagerAdapter
+import code.name.monkey.retromusic.model.Song
+import code.name.monkey.retromusic.util.MusicUtil
+import code.name.monkey.retromusic.util.PreferenceUtil
+import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
+import com.bumptech.glide.Glide
+import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+class AlbumCoverPagerAdapter(
+ fragmentManager: FragmentManager,
+ private val dataSet: List
+) : CustomFragmentStatePagerAdapter(fragmentManager) {
+
+ private var currentColorReceiver: AlbumCoverFragment.ColorReceiver? = null
+ private var currentColorReceiverPosition = -1
+
+ override fun getItem(position: Int): Fragment {
+ return AlbumCoverFragment.newInstance(dataSet[position])
+ }
+
+ override fun getCount(): Int {
+ return dataSet.size
+ }
+
+ override fun instantiateItem(container: ViewGroup, position: Int): Any {
+ val o = super.instantiateItem(container, position)
+ if (currentColorReceiver != null && currentColorReceiverPosition == position) {
+ receiveColor(currentColorReceiver!!, currentColorReceiverPosition)
+ }
+ return o
+ }
+
+ /**
+ * Only the latest passed [AlbumCoverFragment.ColorReceiver] is guaranteed to receive a
+ * response
+ */
+ fun receiveColor(colorReceiver: AlbumCoverFragment.ColorReceiver, position: Int) {
+
+ if (getFragment(position) is AlbumCoverFragment) {
+ val fragment = getFragment(position) as AlbumCoverFragment
+ currentColorReceiver = null
+ currentColorReceiverPosition = -1
+ fragment.receiveColor(colorReceiver, position)
+ } else {
+ currentColorReceiver = colorReceiver
+ currentColorReceiverPosition = position
+ }
+ }
+
+ class AlbumCoverFragment : Fragment() {
+
+ private var isColorReady: Boolean = false
+ private lateinit var color: MediaNotificationProcessor
+ private lateinit var song: Song
+ private var colorReceiver: ColorReceiver? = null
+ private var request: Int = 0
+ private val mainActivity get() = activity as MainActivity
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ if (arguments != null) {
+ song = BundleCompat.getParcelable(requireArguments(), SONG_ARG, Song::class.java)!!
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ val view = inflater.inflate(getLayoutWithPlayerTheme(), container, false)
+ view.setOnClickListener {
+ if (mainActivity.getBottomSheetBehavior().state == STATE_EXPANDED) {
+ showLyricsDialog()
+ }
+ }
+ return view
+ }
+
+ private fun showLyricsDialog() {
+ lifecycleScope.launch(Dispatchers.IO) {
+ val data: String? = MusicUtil.getLyrics(song)
+ withContext(Dispatchers.Main) {
+ MaterialAlertDialogBuilder(
+ requireContext(),
+ com.google.android.material.R.style.ThemeOverlay_MaterialComponents_Dialog_Alert
+ ).apply {
+ setTitle(song.title)
+ setMessage(if (data.isNullOrEmpty()) "No lyrics found" else data)
+ setNegativeButton(R.string.synced_lyrics) { _, _ ->
+ goToLyrics(requireActivity())
+ }
+ show()
+ }
+ }
+ }
+ }
+
+ private fun getLayoutWithPlayerTheme(): Int {
+ return when (PreferenceUtil.nowPlayingScreen) {
+ Card, Fit, Tiny, Classic, Gradient, Full -> R.layout.fragment_album_full_cover
+ Peek -> R.layout.fragment_peek_album_cover
+ else -> {
+ if (PreferenceUtil.isCarouselEffect) {
+ R.layout.fragment_album_carousel_cover
+ } else {
+ when (PreferenceUtil.albumCoverStyle) {
+ AlbumCoverStyle.Normal -> R.layout.fragment_album_cover
+ AlbumCoverStyle.Flat -> R.layout.fragment_album_flat_cover
+ AlbumCoverStyle.Circle -> R.layout.fragment_album_circle_cover
+ AlbumCoverStyle.Card -> R.layout.fragment_album_card_cover
+ AlbumCoverStyle.Full -> R.layout.fragment_album_full_cover
+ AlbumCoverStyle.FullCard -> R.layout.fragment_album_full_card_cover
+ }
+ }
+ }
+ }
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ loadAlbumCover(albumCover = view.findViewById(R.id.player_image))
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ colorReceiver = null
+ }
+
+ private fun loadAlbumCover(albumCover: ImageView) {
+ Glide.with(this)
+ .asBitmapPalette()
+ .songCoverOptions(song)
+ //.checkIgnoreMediaStore()
+ .load(RetroGlideExtension.getSongModel(song))
+ .dontAnimate()
+ .into(object : RetroMusicColoredTarget(albumCover) {
+ override fun onColorReady(colors: MediaNotificationProcessor) {
+ setColor(colors)
+ }
+ })
+ }
+
+ private fun setColor(color: MediaNotificationProcessor) {
+ this.color = color
+ isColorReady = true
+ if (colorReceiver != null) {
+ colorReceiver!!.onColorReady(color, request)
+ colorReceiver = null
+ }
+ }
+
+ internal fun receiveColor(colorReceiver: ColorReceiver, request: Int) {
+ if (isColorReady) {
+ colorReceiver.onColorReady(color, request)
+ } else {
+ this.colorReceiver = colorReceiver
+ this.request = request
+ }
+ }
+
+ interface ColorReceiver {
+ fun onColorReady(color: MediaNotificationProcessor, request: Int)
+ }
+
+ companion object {
+
+ private const val SONG_ARG = "song"
+
+ fun newInstance(song: Song): AlbumCoverFragment {
+ val frag = AlbumCoverFragment()
+ frag.arguments = bundleOf(SONG_ARG to song)
+ return frag
+ }
+ }
+ }
+
+ companion object {
+ val TAG: String = AlbumCoverPagerAdapter::class.java.simpleName
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/album/HorizontalAlbumAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/album/HorizontalAlbumAdapter.kt
new file mode 100644
index 000000000..7a9f7fa6e
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/album/HorizontalAlbumAdapter.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.adapter.album
+
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.FragmentActivity
+import code.name.monkey.retromusic.glide.RetroGlideExtension
+import code.name.monkey.retromusic.glide.RetroGlideExtension.albumCoverOptions
+import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
+import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
+import code.name.monkey.retromusic.helper.HorizontalAdapterHelper
+import code.name.monkey.retromusic.interfaces.IAlbumClickListener
+import code.name.monkey.retromusic.model.Album
+import code.name.monkey.retromusic.util.MusicUtil
+import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
+import com.bumptech.glide.Glide
+
+class HorizontalAlbumAdapter(
+ activity: FragmentActivity,
+ dataSet: List,
+ albumClickListener: IAlbumClickListener
+) : AlbumAdapter(
+ activity, dataSet, HorizontalAdapterHelper.LAYOUT_RES, albumClickListener
+) {
+
+ override fun createViewHolder(view: View, viewType: Int): ViewHolder {
+ val params = view.layoutParams as ViewGroup.MarginLayoutParams
+ HorizontalAdapterHelper.applyMarginToLayoutParams(activity, params, viewType)
+ return ViewHolder(view)
+ }
+
+ override fun setColors(color: MediaNotificationProcessor, holder: ViewHolder) {
+ // holder.title?.setTextColor(ATHUtil.resolveColor(activity, android.R.attr.textColorPrimary))
+ // holder.text?.setTextColor(ATHUtil.resolveColor(activity, android.R.attr.textColorSecondary))
+ }
+
+ override fun loadAlbumCover(album: Album, holder: ViewHolder) {
+ if (holder.image == null) return
+ Glide.with(activity)
+ .asBitmapPalette()
+ .albumCoverOptions(album.safeGetFirstSong())
+ .load(RetroGlideExtension.getSongModel(album.safeGetFirstSong()))
+ .into(object : RetroMusicColoredTarget(holder.image!!) {
+ override fun onColorReady(colors: MediaNotificationProcessor) {
+ setColors(colors, holder)
+ }
+ })
+ }
+
+ override fun getAlbumText(album: Album): String {
+ return MusicUtil.getYearString(album.year)
+ }
+
+ override fun getItemViewType(position: Int): Int {
+ return HorizontalAdapterHelper.getItemViewType(position, itemCount)
+ }
+
+ override fun getItemCount(): Int {
+ return dataSet.size
+ }
+
+ companion object {
+ val TAG: String = AlbumAdapter::class.java.simpleName
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/artist/ArtistAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/artist/ArtistAdapter.kt
new file mode 100644
index 000000000..e1ea2ca4c
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/artist/ArtistAdapter.kt
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.adapter.artist
+
+import android.annotation.SuppressLint
+import android.content.res.ColorStateList
+import android.content.res.Resources
+import android.view.LayoutInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.view.isVisible
+import androidx.fragment.app.FragmentActivity
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
+import code.name.monkey.retromusic.extensions.hide
+import code.name.monkey.retromusic.glide.RetroGlideExtension
+import code.name.monkey.retromusic.glide.RetroGlideExtension.artistImageOptions
+import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
+import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
+import code.name.monkey.retromusic.helper.menu.SongsMenuHelper
+import code.name.monkey.retromusic.interfaces.IAlbumArtistClickListener
+import code.name.monkey.retromusic.interfaces.IArtistClickListener
+import code.name.monkey.retromusic.model.Artist
+import code.name.monkey.retromusic.model.Song
+import code.name.monkey.retromusic.util.MusicUtil
+import code.name.monkey.retromusic.util.PreferenceUtil
+import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
+import com.bumptech.glide.Glide
+import me.zhanghai.android.fastscroll.PopupTextProvider
+
+class ArtistAdapter(
+ override val activity: FragmentActivity,
+ var dataSet: List,
+ var itemLayoutRes: Int,
+ val IArtistClickListener: IArtistClickListener,
+ val IAlbumArtistClickListener: IAlbumArtistClickListener? = null
+) : AbsMultiSelectAdapter(activity, R.menu.menu_media_selection),
+ PopupTextProvider {
+
+ var albumArtistsOnly = false
+
+ init {
+ this.setHasStableIds(true)
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
+ fun swapDataSet(dataSet: List) {
+ this.dataSet = dataSet
+ notifyDataSetChanged()
+ albumArtistsOnly = PreferenceUtil.albumArtistsOnly
+ }
+
+ override fun getItemId(position: Int): Long {
+ return dataSet[position].id
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val view =
+ try {
+ LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false)
+ } catch (e: Resources.NotFoundException) {
+ LayoutInflater.from(activity).inflate(R.layout.item_grid_circle, parent, false)
+ }
+ return createViewHolder(view)
+ }
+
+ private fun createViewHolder(view: View): ViewHolder {
+ return ViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val artist = dataSet[position]
+ val isChecked = isChecked(artist)
+ holder.itemView.isActivated = isChecked
+ holder.title?.text = artist.name
+ holder.text?.hide()
+ val transitionName =
+ if (albumArtistsOnly) artist.name else artist.id.toString()
+ if (holder.imageContainer != null) {
+ holder.imageContainer?.transitionName = transitionName
+ } else {
+ holder.image?.transitionName = transitionName
+ }
+ loadArtistImage(artist, holder)
+ }
+
+ private fun setColors(processor: MediaNotificationProcessor, holder: ViewHolder) {
+ holder.mask?.backgroundTintList = ColorStateList.valueOf(processor.primaryTextColor)
+ if (holder.paletteColorContainer != null) {
+ holder.paletteColorContainer?.setBackgroundColor(processor.backgroundColor)
+ holder.title?.setTextColor(processor.primaryTextColor)
+ }
+ holder.imageContainerCard?.setCardBackgroundColor(processor.backgroundColor)
+ }
+
+ private fun loadArtistImage(artist: Artist, holder: ViewHolder) {
+ if (holder.image == null) {
+ return
+ }
+ Glide.with(activity)
+ .asBitmapPalette()
+ .artistImageOptions(artist)
+ .load(RetroGlideExtension.getArtistModel(artist))
+ .transition(RetroGlideExtension.getDefaultTransition())
+ .into(object : RetroMusicColoredTarget(holder.image!!) {
+ override fun onColorReady(colors: MediaNotificationProcessor) {
+ setColors(colors, holder)
+ }
+ })
+ }
+
+ override fun getItemCount(): Int {
+ return dataSet.size
+ }
+
+ override fun getIdentifier(position: Int): Artist {
+ return dataSet[position]
+ }
+
+ override fun getName(model: Artist): String {
+ return model.name
+ }
+
+ override fun onMultipleItemAction(
+ menuItem: MenuItem,
+ selection: List
+ ) {
+ SongsMenuHelper.handleMenuClick(activity, getSongList(selection), menuItem.itemId)
+ }
+
+ private fun getSongList(artists: List): List {
+ val songs = ArrayList()
+ for (artist in artists) {
+ songs.addAll(artist.songs) // maybe async in future?
+ }
+ return songs
+ }
+
+ override fun getPopupText(position: Int): String {
+ return getSectionName(position)
+ }
+
+ private fun getSectionName(position: Int): String {
+ return MusicUtil.getSectionName(dataSet[position].name)
+ }
+
+ inner class ViewHolder(itemView: View) : code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder(itemView) {
+
+ init {
+ menu?.isVisible = false
+ }
+
+ override fun onClick(v: View?) {
+ super.onClick(v)
+ if (isInQuickSelectMode) {
+ toggleChecked(layoutPosition)
+ } else {
+ val artist = dataSet[layoutPosition]
+ image?.let {
+ if (albumArtistsOnly && IAlbumArtistClickListener != null) {
+ IAlbumArtistClickListener.onAlbumArtist(artist.name, imageContainer ?: it)
+ } else {
+ IArtistClickListener.onArtist(artist.id, imageContainer ?: it)
+ }
+ }
+ }
+ }
+
+ override fun onLongClick(v: View?): Boolean {
+ return toggleChecked(layoutPosition)
+ }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/backup/BackupAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/backup/BackupAdapter.kt
new file mode 100644
index 000000000..adfcecf1b
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/backup/BackupAdapter.kt
@@ -0,0 +1,65 @@
+package code.name.monkey.retromusic.adapter.backup
+
+import android.annotation.SuppressLint
+import android.view.LayoutInflater
+import android.view.MenuItem
+import android.view.ViewGroup
+import androidx.appcompat.widget.PopupMenu
+import androidx.fragment.app.FragmentActivity
+import androidx.recyclerview.widget.RecyclerView
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.databinding.ItemListBackupBinding
+import java.io.File
+
+
+class BackupAdapter(
+ val activity: FragmentActivity,
+ var dataSet: MutableList,
+ val backupClickedListener: BackupClickedListener
+) : RecyclerView.Adapter() {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ return ViewHolder(
+ ItemListBackupBinding.inflate(LayoutInflater.from(activity), parent, false)
+ )
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ holder.binding.title.text = dataSet[position].nameWithoutExtension
+ }
+
+ override fun getItemCount(): Int = dataSet.size
+
+ @SuppressLint("NotifyDataSetChanged")
+ fun swapDataset(dataSet: List) {
+ this.dataSet = ArrayList(dataSet)
+ notifyDataSetChanged()
+ }
+
+ inner class ViewHolder(val binding: ItemListBackupBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+
+ init {
+ binding.menu.setOnClickListener { view ->
+ val popupMenu = PopupMenu(activity, view)
+ popupMenu.inflate(R.menu.menu_backup)
+ popupMenu.setOnMenuItemClickListener { menuItem ->
+ return@setOnMenuItemClickListener backupClickedListener.onBackupMenuClicked(
+ dataSet[bindingAdapterPosition],
+ menuItem
+ )
+ }
+ popupMenu.show()
+ }
+ itemView.setOnClickListener {
+ backupClickedListener.onBackupClicked(dataSet[bindingAdapterPosition])
+ }
+ }
+ }
+
+ interface BackupClickedListener {
+ fun onBackupClicked(file: File)
+
+ fun onBackupMenuClicked(file: File, menuItem: MenuItem): Boolean
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/base/AbsMultiSelectAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/base/AbsMultiSelectAdapter.kt
new file mode 100644
index 000000000..bb7b8491c
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/base/AbsMultiSelectAdapter.kt
@@ -0,0 +1,131 @@
+package code.name.monkey.retromusic.adapter.base
+
+import android.graphics.Color
+import android.view.ActionMode
+import android.view.Menu
+import android.view.MenuItem
+import androidx.activity.OnBackPressedCallback
+import androidx.annotation.MenuRes
+import androidx.fragment.app.FragmentActivity
+import androidx.recyclerview.widget.RecyclerView
+import code.name.monkey.appthemehelper.util.VersionUtils
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.databinding.NumberRollViewBinding
+import code.name.monkey.retromusic.views.NumberRollView
+
+abstract class AbsMultiSelectAdapter(
+ open val activity: FragmentActivity, @MenuRes menuRes: Int,
+) : RecyclerView.Adapter(), ActionMode.Callback {
+ var actionMode: ActionMode? = null
+ private val checked: MutableList
+ private var menuRes: Int
+
+ override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
+ val inflater = mode?.menuInflater
+ inflater?.inflate(menuRes, menu)
+ return true
+ }
+
+ override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
+ return false
+ }
+
+ override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
+ if (item?.itemId == R.id.action_multi_select_adapter_check_all) {
+ checkAll()
+ } else {
+ onMultipleItemAction(item!!, ArrayList(checked))
+ actionMode?.finish()
+ clearChecked()
+ }
+ return true
+ }
+
+ override fun onDestroyActionMode(mode: ActionMode?) {
+ clearChecked()
+ activity.window.statusBarColor = when {
+ VersionUtils.hasMarshmallow() -> Color.TRANSPARENT
+ else -> Color.BLACK
+ }
+ actionMode = null
+ onBackPressedCallback.remove()
+ }
+
+ private fun checkAll() {
+ if (actionMode != null) {
+ checked.clear()
+ for (i in 0 until itemCount) {
+ val identifier = getIdentifier(i)
+ if (identifier != null) {
+ checked.add(identifier)
+ }
+ }
+ notifyDataSetChanged()
+ updateCab()
+ }
+ }
+
+ protected abstract fun getIdentifier(position: Int): I?
+
+ protected abstract fun getName(model: I): String?
+
+ protected fun isChecked(identifier: I): Boolean {
+ return checked.contains(identifier)
+ }
+
+ protected val isInQuickSelectMode: Boolean
+ get() = actionMode != null
+
+ protected abstract fun onMultipleItemAction(menuItem: MenuItem, selection: List)
+ protected fun setMultiSelectMenuRes(@MenuRes menuRes: Int) {
+ this.menuRes = menuRes
+ }
+
+ protected fun toggleChecked(position: Int): Boolean {
+ val identifier = getIdentifier(position) ?: return false
+ if (!checked.remove(identifier)) {
+ checked.add(identifier)
+ }
+ notifyItemChanged(position)
+ updateCab()
+ return true
+ }
+
+ private fun clearChecked() {
+ checked.clear()
+ notifyDataSetChanged()
+ }
+
+ private fun updateCab() {
+ if (actionMode == null) {
+ actionMode = activity.startActionMode(this)?.apply {
+ customView = NumberRollViewBinding.inflate(activity.layoutInflater).root
+ }
+ activity.onBackPressedDispatcher.addCallback(onBackPressedCallback)
+ }
+ val size = checked.size
+ when {
+ size <= 0 -> {
+ actionMode?.finish()
+ }
+ else -> {
+ actionMode?.customView?.findViewById(R.id.selection_mode_number)
+ ?.setNumber(size, true)
+ }
+ }
+ }
+
+ init {
+ checked = ArrayList()
+ this.menuRes = menuRes
+ }
+
+ private val onBackPressedCallback = object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ if (actionMode != null) {
+ actionMode?.finish()
+ remove()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/base/MediaEntryViewHolder.java b/app/src/main/java/code/name/monkey/retromusic/adapter/base/MediaEntryViewHolder.java
new file mode 100644
index 000000000..d68186c0f
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/base/MediaEntryViewHolder.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2019 Hemanth Savarala.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by
+ * the Free Software Foundation either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ */
+
+package code.name.monkey.retromusic.adapter.base;
+
+import android.graphics.Color;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.widget.AppCompatImageView;
+
+import com.google.android.material.card.MaterialCardView;
+import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractDraggableSwipeableItemViewHolder;
+
+import code.name.monkey.retromusic.R;
+
+public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHolder
+ implements View.OnLongClickListener, View.OnClickListener {
+
+ @Nullable
+ public View dragView;
+
+ @Nullable
+ public View dummyContainer;
+
+ @Nullable
+ public ImageView image;
+
+ @Nullable
+ public MaterialCardView imageContainerCard;
+
+ @Nullable
+ public FrameLayout imageContainer;
+
+ @Nullable
+ public TextView imageText;
+
+ @Nullable
+ public MaterialCardView imageTextContainer;
+
+ @Nullable
+ public View mask;
+
+ @Nullable
+ public AppCompatImageView menu;
+
+ @Nullable
+ public View paletteColorContainer;
+
+ @Nullable
+ public TextView text;
+
+ @Nullable
+ public TextView text2;
+
+ @Nullable
+ public TextView time;
+
+ @Nullable
+ public TextView title;
+
+ public MediaEntryViewHolder(@NonNull View itemView) {
+ super(itemView);
+ title = itemView.findViewById(R.id.title);
+ text = itemView.findViewById(R.id.text);
+ text2 = itemView.findViewById(R.id.text2);
+
+ image = itemView.findViewById(R.id.image);
+ time = itemView.findViewById(R.id.time);
+
+ imageText = itemView.findViewById(R.id.imageText);
+ imageTextContainer = itemView.findViewById(R.id.imageTextContainer);
+ imageContainerCard = itemView.findViewById(R.id.imageContainerCard);
+ imageContainer = itemView.findViewById(R.id.imageContainer);
+
+ menu = itemView.findViewById(R.id.menu);
+ dragView = itemView.findViewById(R.id.drag_view);
+ paletteColorContainer = itemView.findViewById(R.id.paletteColorContainer);
+ mask = itemView.findViewById(R.id.mask);
+ dummyContainer = itemView.findViewById(R.id.dummy_view);
+
+ if (imageContainerCard != null) {
+ imageContainerCard.setCardBackgroundColor(Color.TRANSPARENT);
+ }
+ itemView.setOnClickListener(this);
+ itemView.setOnLongClickListener(this);
+ }
+
+ @Nullable
+ @Override
+ public View getSwipeableContainerView() {
+ return null;
+ }
+
+ @Override
+ public void onClick(View v) {
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ return false;
+ }
+
+ public void setImageTransitionName(@NonNull String transitionName) {
+ itemView.setTransitionName(transitionName);
+ /* if (imageContainerCard != null) {
+ imageContainerCard.setTransitionName(transitionName);
+ }
+ if (image != null) {
+ image.setTransitionName(transitionName);
+ }*/
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/LegacyPlaylistAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/LegacyPlaylistAdapter.kt
new file mode 100644
index 000000000..19d681ecf
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/LegacyPlaylistAdapter.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.adapter.playlist
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.FragmentActivity
+import androidx.recyclerview.widget.RecyclerView
+import code.name.monkey.retromusic.model.Playlist
+import code.name.monkey.retromusic.util.MusicUtil
+
+class LegacyPlaylistAdapter(
+ private val activity: FragmentActivity,
+ private var list: List,
+ private val layoutRes: Int,
+ private val playlistClickListener: PlaylistClickListener
+) :
+ RecyclerView.Adapter() {
+
+ fun swapData(list: List) {
+ this.list = list
+ notifyDataSetChanged()
+ }
+
+ class ViewHolder(itemView: View) : code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder(itemView)
+
+ override fun onCreateViewHolder(
+ parent: ViewGroup,
+ viewType: Int
+ ): ViewHolder {
+ return ViewHolder(
+ LayoutInflater.from(parent.context).inflate(layoutRes, parent, false)
+ )
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val playlist: Playlist = list[position]
+ holder.title?.text = playlist.name
+ holder.text?.text = MusicUtil.getPlaylistInfoString(activity, playlist.getSongs())
+ holder.itemView.setOnClickListener {
+ playlistClickListener.onPlaylistClick(playlist)
+ }
+ }
+
+ override fun getItemCount(): Int {
+ return list.size
+ }
+
+ interface PlaylistClickListener {
+ fun onPlaylistClick(playlist: Playlist)
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/PlaylistAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/PlaylistAdapter.kt
new file mode 100755
index 000000000..6e5b0c001
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/PlaylistAdapter.kt
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.adapter.playlist
+
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.view.LayoutInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import androidx.appcompat.widget.PopupMenu
+import androidx.core.view.isGone
+import androidx.core.view.setPadding
+import androidx.fragment.app.FragmentActivity
+import code.name.monkey.appthemehelper.util.ATHUtil
+import code.name.monkey.appthemehelper.util.TintHelper
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
+import code.name.monkey.retromusic.db.PlaylistEntity
+import code.name.monkey.retromusic.db.PlaylistWithSongs
+import code.name.monkey.retromusic.db.toSongs
+import code.name.monkey.retromusic.extensions.dipToPix
+import code.name.monkey.retromusic.glide.RetroGlideExtension.playlistOptions
+import code.name.monkey.retromusic.glide.playlistPreview.PlaylistPreview
+import code.name.monkey.retromusic.helper.SortOrder.PlaylistSortOrder
+import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper
+import code.name.monkey.retromusic.helper.menu.SongsMenuHelper
+import code.name.monkey.retromusic.interfaces.IPlaylistClickListener
+import code.name.monkey.retromusic.model.Song
+import code.name.monkey.retromusic.util.MusicUtil
+import code.name.monkey.retromusic.util.PreferenceUtil
+import com.bumptech.glide.Glide
+import me.zhanghai.android.fastscroll.PopupTextProvider
+
+class PlaylistAdapter(
+ override val activity: FragmentActivity,
+ var dataSet: List,
+ private var itemLayoutRes: Int,
+ private val listener: IPlaylistClickListener
+) : AbsMultiSelectAdapter(
+ activity,
+ R.menu.menu_playlists_selection
+), PopupTextProvider {
+
+ init {
+ setHasStableIds(true)
+ }
+
+ fun swapDataSet(dataSet: List) {
+ this.dataSet = dataSet
+ notifyDataSetChanged()
+ }
+
+ override fun getItemId(position: Int): Long {
+ return dataSet[position].playlistEntity.playListId
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val view = LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false)
+ return createViewHolder(view)
+ }
+
+ private fun createViewHolder(view: View): ViewHolder {
+ return ViewHolder(view)
+ }
+
+ private fun getPlaylistTitle(playlist: PlaylistEntity): String {
+ return playlist.playlistName.ifEmpty { "-" }
+ }
+
+ private fun getPlaylistText(playlist: PlaylistWithSongs): String {
+ return MusicUtil.getPlaylistInfoString(activity, playlist.songs.toSongs())
+ }
+
+ override fun getPopupText(position: Int): String {
+ val sectionName: String = when (PreferenceUtil.playlistSortOrder) {
+ PlaylistSortOrder.PLAYLIST_A_Z, PlaylistSortOrder.PLAYLIST_Z_A -> dataSet[position].playlistEntity.playlistName
+ PlaylistSortOrder.PLAYLIST_SONG_COUNT, PlaylistSortOrder.PLAYLIST_SONG_COUNT_DESC -> dataSet[position].songs.size.toString()
+ else -> {
+ return ""
+ }
+ }
+ return MusicUtil.getSectionName(sectionName)
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val playlist = dataSet[position]
+ holder.itemView.isActivated = isChecked(playlist)
+ holder.title?.text = getPlaylistTitle(playlist.playlistEntity)
+ holder.text?.text = getPlaylistText(playlist)
+ holder.menu?.isGone = isChecked(playlist)
+ if (itemLayoutRes == R.layout.item_list) {
+ holder.image?.setPadding(activity.dipToPix(8F).toInt())
+ holder.image?.setImageDrawable(getIconRes())
+ } else {
+ Glide.with(activity)
+ .load(PlaylistPreview(playlist))
+ .playlistOptions()
+ .into(holder.image!!)
+ }
+ }
+
+ private fun getIconRes(): Drawable = TintHelper.createTintedDrawable(
+ activity,
+ R.drawable.ic_playlist_play,
+ ATHUtil.resolveColor(activity, android.R.attr.colorControlNormal)
+ )
+
+ override fun getItemCount(): Int {
+ return dataSet.size
+ }
+
+ override fun getIdentifier(position: Int): PlaylistWithSongs {
+ return dataSet[position]
+ }
+
+ override fun getName(model: PlaylistWithSongs): String {
+ return model.playlistEntity.playlistName
+ }
+
+ override fun onMultipleItemAction(menuItem: MenuItem, selection: List) {
+ when (menuItem.itemId) {
+ else -> SongsMenuHelper.handleMenuClick(
+ activity,
+ getSongList(selection),
+ menuItem.itemId
+ )
+ }
+ }
+
+ private fun getSongList(playlists: List): List {
+ val songs = mutableListOf()
+ playlists.forEach {
+ songs.addAll(it.songs.toSongs())
+ }
+ return songs
+ }
+
+ inner class ViewHolder(itemView: View) : code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder(itemView) {
+ init {
+ menu?.setOnClickListener { view ->
+ val popupMenu = PopupMenu(activity, view)
+ popupMenu.inflate(R.menu.menu_item_playlist)
+ popupMenu.setOnMenuItemClickListener { item ->
+ PlaylistMenuHelper.handleMenuClick(activity, dataSet[layoutPosition], item)
+ }
+ popupMenu.show()
+ }
+
+ imageTextContainer?.apply {
+ cardElevation = 0f
+ setCardBackgroundColor(Color.TRANSPARENT)
+ }
+ }
+
+ override fun onClick(v: View?) {
+ if (isInQuickSelectMode) {
+ toggleChecked(layoutPosition)
+ } else {
+ itemView.transitionName = "playlist"
+ listener.onPlaylistClick(dataSet[layoutPosition], itemView)
+ }
+ }
+
+ override fun onLongClick(v: View?): Boolean {
+ toggleChecked(layoutPosition)
+ return true
+ }
+ }
+
+ companion object {
+ val TAG: String = PlaylistAdapter::class.java.simpleName
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/song/AbsOffsetSongAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/song/AbsOffsetSongAdapter.kt
new file mode 100644
index 000000000..2d5fd724f
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/song/AbsOffsetSongAdapter.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.adapter.song
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.LayoutRes
+import androidx.fragment.app.FragmentActivity
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.helper.MusicPlayerRemote
+import code.name.monkey.retromusic.model.Song
+
+abstract class AbsOffsetSongAdapter(
+ activity: FragmentActivity,
+ dataSet: MutableList,
+ @LayoutRes itemLayoutRes: Int
+) : SongAdapter(activity, dataSet, itemLayoutRes) {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SongAdapter.ViewHolder {
+ if (viewType == OFFSET_ITEM) {
+ val view = LayoutInflater.from(activity)
+ .inflate(R.layout.item_list_quick_actions, parent, false)
+ return createViewHolder(view)
+ }
+ return super.onCreateViewHolder(parent, viewType)
+ }
+
+ override fun createViewHolder(view: View): SongAdapter.ViewHolder {
+ return ViewHolder(view)
+ }
+
+ override fun getItemId(position: Int): Long {
+ var positionFinal = position
+ positionFinal--
+ return if (positionFinal < 0) -2 else super.getItemId(positionFinal)
+ }
+
+ override fun getIdentifier(position: Int): Song? {
+ var positionFinal = position
+ positionFinal--
+ return if (positionFinal < 0) null else super.getIdentifier(positionFinal)
+ }
+
+ override fun getItemCount(): Int {
+ val superItemCount = super.getItemCount()
+ return if (superItemCount == 0) 0 else superItemCount + 1
+ }
+
+ override fun getItemViewType(position: Int): Int {
+ return if (position == 0) OFFSET_ITEM else SONG
+ }
+
+ open inner class ViewHolder(itemView: View) : SongAdapter.ViewHolder(itemView) {
+
+ override // could also return null, just to be safe return empty song
+ val song: Song
+ get() = if (itemViewType == OFFSET_ITEM) Song.emptySong else dataSet[layoutPosition - 1]
+
+ override fun onClick(v: View?) {
+ if (isInQuickSelectMode && itemViewType != OFFSET_ITEM) {
+ toggleChecked(layoutPosition)
+ } else {
+ MusicPlayerRemote.openQueue(dataSet, layoutPosition - 1, true)
+ }
+ }
+
+ override fun onLongClick(v: View?): Boolean {
+ if (itemViewType == OFFSET_ITEM) return false
+ toggleChecked(layoutPosition)
+ return true
+ }
+ }
+
+ companion object {
+ const val OFFSET_ITEM = 0
+ const val SONG = 1
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/song/OrderablePlaylistSongAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/song/OrderablePlaylistSongAdapter.kt
new file mode 100644
index 000000000..bf85fe28b
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/song/OrderablePlaylistSongAdapter.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.adapter.song
+
+import android.view.MenuItem
+import android.view.View
+import androidx.core.view.isVisible
+import androidx.fragment.app.FragmentActivity
+import androidx.lifecycle.lifecycleScope
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.db.PlaylistEntity
+import code.name.monkey.retromusic.db.toSongEntity
+import code.name.monkey.retromusic.db.toSongsEntity
+import code.name.monkey.retromusic.dialogs.RemoveSongFromPlaylistDialog
+import code.name.monkey.retromusic.fragments.LibraryViewModel
+import code.name.monkey.retromusic.model.Song
+import code.name.monkey.retromusic.util.ViewUtil
+import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter
+import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import org.koin.androidx.viewmodel.ext.android.viewModel
+
+class OrderablePlaylistSongAdapter(
+ private val playlistId: Long,
+ activity: FragmentActivity,
+ dataSet: MutableList,
+ itemLayoutRes: Int,
+) : SongAdapter(activity, dataSet, itemLayoutRes),
+ DraggableItemAdapter {
+
+ val libraryViewModel: LibraryViewModel by activity.viewModel()
+
+ init {
+ this.setHasStableIds(true)
+ this.setMultiSelectMenuRes(R.menu.menu_playlists_songs_selection)
+ }
+
+ override fun getItemId(position: Int): Long {
+ // requires static value, it means need to keep the same value
+ // even if the item position has been changed.
+ return dataSet[position].id
+ }
+
+ override fun createViewHolder(view: View): SongAdapter.ViewHolder {
+ return ViewHolder(view)
+ }
+
+ override fun onMultipleItemAction(menuItem: MenuItem, selection: List) {
+ when (menuItem.itemId) {
+ R.id.action_remove_from_playlist -> RemoveSongFromPlaylistDialog.create(
+ selection.toSongsEntity(
+ playlistId
+ )
+ )
+ .show(activity.supportFragmentManager, "REMOVE_FROM_PLAYLIST")
+
+ else -> super.onMultipleItemAction(menuItem, selection)
+ }
+ }
+
+ inner class ViewHolder(itemView: View) : SongAdapter.ViewHolder(itemView) {
+
+ override var songMenuRes: Int
+ get() = R.menu.menu_item_playlist_song
+ set(value) {
+ super.songMenuRes = value
+ }
+
+ override fun onSongMenuItemClick(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.action_remove_from_playlist -> {
+ RemoveSongFromPlaylistDialog.create(song.toSongEntity(playlistId))
+ .show(activity.supportFragmentManager, "REMOVE_FROM_PLAYLIST")
+ return true
+ }
+ }
+ return super.onSongMenuItemClick(item)
+ }
+
+ init {
+ dragView?.isVisible = true
+ }
+ }
+
+ override fun onCheckCanStartDrag(holder: ViewHolder, position: Int, x: Int, y: Int): Boolean {
+ if (isInQuickSelectMode) {
+ return false
+ }
+ return ViewUtil.hitTest(holder.imageText!!, x, y) || ViewUtil.hitTest(
+ holder.dragView!!,
+ x,
+ y
+ )
+ }
+
+ override fun onMoveItem(fromPosition: Int, toPosition: Int) {
+ dataSet.add(toPosition, dataSet.removeAt(fromPosition))
+ }
+
+ override fun onGetItemDraggableRange(holder: ViewHolder, position: Int): ItemDraggableRange? {
+ return null
+ }
+
+ override fun onCheckCanDrop(draggingPosition: Int, dropPosition: Int): Boolean {
+ return true
+ }
+
+ override fun onItemDragStarted(position: Int) {
+ notifyDataSetChanged()
+ }
+
+ override fun onItemDragFinished(fromPosition: Int, toPosition: Int, result: Boolean) {
+ notifyDataSetChanged()
+ }
+
+ fun saveSongs(playlistEntity: PlaylistEntity) {
+ activity.lifecycleScope.launch(Dispatchers.IO) {
+ libraryViewModel.insertSongs(dataSet.toSongsEntity(playlistEntity))
+ }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/song/PlayingQueueAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/song/PlayingQueueAdapter.kt
new file mode 100644
index 000000000..2d387be44
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/song/PlayingQueueAdapter.kt
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.adapter.song
+
+import android.view.MenuItem
+import android.view.View
+import androidx.core.view.isVisible
+import androidx.fragment.app.FragmentActivity
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.glide.RetroGlideExtension
+import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
+import code.name.monkey.retromusic.helper.MusicPlayerRemote
+import code.name.monkey.retromusic.helper.MusicPlayerRemote.isPlaying
+import code.name.monkey.retromusic.helper.MusicPlayerRemote.playNextSong
+import code.name.monkey.retromusic.helper.MusicPlayerRemote.removeFromQueue
+import code.name.monkey.retromusic.model.Song
+import code.name.monkey.retromusic.util.MusicUtil
+import code.name.monkey.retromusic.util.ViewUtil
+import com.bumptech.glide.Glide
+import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter
+import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange
+import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags
+import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemAdapter
+import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemConstants
+import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultAction
+import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionDefault
+import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionRemoveItem
+import me.zhanghai.android.fastscroll.PopupTextProvider
+
+class PlayingQueueAdapter(
+ activity: FragmentActivity,
+ dataSet: MutableList,
+ private var current: Int,
+ itemLayoutRes: Int,
+) : SongAdapter(activity, dataSet, itemLayoutRes),
+ DraggableItemAdapter,
+ SwipeableItemAdapter,
+ PopupTextProvider {
+
+ private var songToRemove: Song? = null
+
+ override fun createViewHolder(view: View): SongAdapter.ViewHolder {
+ return ViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: SongAdapter.ViewHolder, position: Int) {
+ super.onBindViewHolder(holder, position)
+ val song = dataSet[position]
+ holder.time?.text = MusicUtil.getReadableDurationString(song.duration)
+ if (holder.itemViewType == HISTORY || holder.itemViewType == CURRENT) {
+ setAlpha(holder, 0.5f)
+ }
+ }
+
+ override fun getItemViewType(position: Int): Int {
+ if (position < current) {
+ return HISTORY
+ } else if (position > current) {
+ return UP_NEXT
+ }
+ return CURRENT
+ }
+
+ override fun loadAlbumCover(song: Song, holder: SongAdapter.ViewHolder) {
+ if (holder.image == null) {
+ return
+ }
+ Glide.with(activity)
+ .load(RetroGlideExtension.getSongModel(song))
+ .songCoverOptions(song)
+ .into(holder.image!!)
+ }
+
+ fun swapDataSet(dataSet: List, position: Int) {
+ this.dataSet = dataSet.toMutableList()
+ current = position
+ notifyDataSetChanged()
+ }
+
+ fun setCurrent(current: Int) {
+ this.current = current
+ notifyDataSetChanged()
+ }
+
+ private fun setAlpha(holder: SongAdapter.ViewHolder, alpha: Float) {
+ holder.image?.alpha = alpha
+ holder.title?.alpha = alpha
+ holder.text?.alpha = alpha
+ holder.paletteColorContainer?.alpha = alpha
+ holder.dragView?.alpha = alpha
+ holder.menu?.alpha = alpha
+ }
+
+ override fun getPopupText(position: Int): String {
+ return MusicUtil.getSectionName(dataSet[position].title)
+ }
+
+ override fun onCheckCanStartDrag(holder: ViewHolder, position: Int, x: Int, y: Int): Boolean {
+ return ViewUtil.hitTest(holder.imageText!!, x, y) || ViewUtil.hitTest(
+ holder.dragView!!,
+ x,
+ y
+ )
+ }
+
+ override fun onGetItemDraggableRange(holder: ViewHolder, position: Int): ItemDraggableRange? {
+ return null
+ }
+
+ override fun onMoveItem(fromPosition: Int, toPosition: Int) {
+ MusicPlayerRemote.moveSong(fromPosition, toPosition)
+ }
+
+ override fun onCheckCanDrop(draggingPosition: Int, dropPosition: Int): Boolean {
+ return true
+ }
+
+ override fun onItemDragStarted(position: Int) {
+ notifyDataSetChanged()
+ }
+
+ override fun onItemDragFinished(fromPosition: Int, toPosition: Int, result: Boolean) {
+ notifyDataSetChanged()
+ }
+
+ fun setSongToRemove(song: Song) {
+ songToRemove = song
+ }
+
+ inner class ViewHolder(itemView: View) : SongAdapter.ViewHolder(itemView) {
+ @DraggableItemStateFlags
+ private var mDragStateFlags: Int = 0
+
+ override var songMenuRes: Int
+ get() = R.menu.menu_item_playing_queue_song
+ set(value) {
+ super.songMenuRes = value
+ }
+
+ init {
+ dragView?.isVisible = true
+ }
+
+ override fun onClick(v: View?) {
+ if (isInQuickSelectMode) {
+ toggleChecked(layoutPosition)
+ } else {
+ MusicPlayerRemote.playSongAt(layoutPosition)
+ }
+ }
+
+ override fun onSongMenuItemClick(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.action_remove_from_playing_queue -> {
+ removeFromQueue(layoutPosition)
+ return true
+ }
+ }
+ return super.onSongMenuItemClick(item)
+ }
+
+ @DraggableItemStateFlags
+ override fun getDragStateFlags(): Int {
+ return mDragStateFlags
+ }
+
+ override fun setDragStateFlags(@DraggableItemStateFlags flags: Int) {
+ mDragStateFlags = flags
+ }
+
+ override fun getSwipeableContainerView(): View {
+ return dummyContainer!!
+ }
+ }
+
+ companion object {
+
+ private const val HISTORY = 0
+ private const val CURRENT = 1
+ private const val UP_NEXT = 2
+ }
+
+ override fun onSwipeItem(holder: ViewHolder, position: Int, result: Int): SwipeResultAction {
+ return if (result == SwipeableItemConstants.RESULT_CANCELED) {
+ SwipeResultActionDefault()
+ } else {
+ SwipedResultActionRemoveItem(this, position)
+ }
+ }
+
+ override fun onGetSwipeReactionType(holder: ViewHolder, position: Int, x: Int, y: Int): Int {
+ return if (onCheckCanStartDrag(holder, position, x, y)) {
+ SwipeableItemConstants.REACTION_CAN_NOT_SWIPE_BOTH_H
+ } else {
+ SwipeableItemConstants.REACTION_CAN_SWIPE_BOTH_H
+ }
+ }
+
+ override fun onSwipeItemStarted(holder: ViewHolder, p1: Int) {
+ }
+
+ override fun onSetSwipeBackground(holder: ViewHolder, position: Int, result: Int) {
+ }
+
+ internal class SwipedResultActionRemoveItem(
+ private val adapter: PlayingQueueAdapter,
+ private val position: Int,
+ ) : SwipeResultActionRemoveItem() {
+
+ private var songToRemove: Song? = null
+ override fun onPerformAction() {
+ // currentlyShownSnackbar = null
+ }
+
+ override fun onSlideAnimationEnd() {
+ // initializeSnackBar(adapter, position, activity, isPlaying)
+ songToRemove = adapter.dataSet[position]
+ // If song removed was the playing song, then play the next song
+ if (isPlaying(songToRemove!!)) {
+ playNextSong()
+ }
+ // Swipe animation is much smoother when we do the heavy lifting after it's completed
+ adapter.setSongToRemove(songToRemove!!)
+ removeFromQueue(songToRemove!!)
+ }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/song/ShuffleButtonSongAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/song/ShuffleButtonSongAdapter.kt
new file mode 100644
index 000000000..82ba1a7f7
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/song/ShuffleButtonSongAdapter.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.adapter.song
+
+import android.view.View
+import androidx.core.view.isVisible
+import androidx.fragment.app.FragmentActivity
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.extensions.accentColor
+import code.name.monkey.retromusic.extensions.accentOutlineColor
+import code.name.monkey.retromusic.helper.MusicPlayerRemote
+import code.name.monkey.retromusic.model.Song
+import code.name.monkey.retromusic.util.PreferenceUtil
+import code.name.monkey.retromusic.util.RetroUtil
+import com.google.android.material.button.MaterialButton
+
+class ShuffleButtonSongAdapter(
+ activity: FragmentActivity,
+ dataSet: MutableList,
+ itemLayoutRes: Int
+) : AbsOffsetSongAdapter(activity, dataSet, itemLayoutRes) {
+
+
+ override fun createViewHolder(view: View): SongAdapter.ViewHolder {
+ return ViewHolder(view)
+ }
+
+ override fun getItemViewType(position: Int): Int {
+ return if (position == 0) OFFSET_ITEM else SONG
+ }
+
+ override fun onBindViewHolder(holder: SongAdapter.ViewHolder, position: Int) {
+ if (holder.itemViewType == OFFSET_ITEM) {
+ val viewHolder = holder as ViewHolder
+ viewHolder.playAction?.let {
+ it.setOnClickListener {
+ MusicPlayerRemote.openQueue(dataSet, 0, true)
+ }
+ it.accentOutlineColor()
+ }
+ viewHolder.shuffleAction?.let {
+ it.setOnClickListener {
+ MusicPlayerRemote.openAndShuffleQueue(dataSet, true)
+ }
+ it.accentColor()
+ }
+ } else {
+ super.onBindViewHolder(holder, position - 1)
+ val landscape = RetroUtil.isLandscape
+ if ((PreferenceUtil.songGridSize > 2 && !landscape) || (PreferenceUtil.songGridSizeLand > 5 && landscape)) {
+ holder.menu?.isVisible = false
+ }
+ }
+ }
+
+ inner class ViewHolder(itemView: View) : AbsOffsetSongAdapter.ViewHolder(itemView) {
+ val playAction: MaterialButton? = itemView.findViewById(R.id.playAction)
+ val shuffleAction: MaterialButton? = itemView.findViewById(R.id.shuffleAction)
+
+ override fun onClick(v: View?) {
+ if (itemViewType == OFFSET_ITEM) {
+ MusicPlayerRemote.openAndShuffleQueue(dataSet, true)
+ return
+ }
+ super.onClick(v)
+ }
+ }
+
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/song/SimpleSongAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/song/SimpleSongAdapter.kt
new file mode 100755
index 000000000..f1f50e627
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/song/SimpleSongAdapter.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.adapter.song
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.fragment.app.FragmentActivity
+import code.name.monkey.retromusic.model.Song
+import code.name.monkey.retromusic.util.MusicUtil
+
+class SimpleSongAdapter(
+ context: FragmentActivity,
+ songs: ArrayList,
+ layoutRes: Int
+) : SongAdapter(context, songs, layoutRes) {
+
+ override fun swapDataSet(dataSet: List) {
+ this.dataSet = dataSet.toMutableList()
+ notifyDataSetChanged()
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ return ViewHolder(LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false))
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ super.onBindViewHolder(holder, position)
+ val fixedTrackNumber = MusicUtil.getFixedTrackNumber(dataSet[position].trackNumber)
+ val trackAndTime = (if (fixedTrackNumber > 0) "$fixedTrackNumber | " else "") +
+ MusicUtil.getReadableDurationString(dataSet[position].duration)
+
+ holder.time?.text = trackAndTime
+ holder.text2?.text = dataSet[position].artistName
+ }
+
+ override fun getItemCount(): Int {
+ return dataSet.size
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/song/SongAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/song/SongAdapter.kt
new file mode 100644
index 000000000..7b7b7bf99
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/song/SongAdapter.kt
@@ -0,0 +1,232 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.adapter.song
+
+import android.content.res.ColorStateList
+import android.content.res.Resources
+import android.view.LayoutInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.os.bundleOf
+import androidx.core.view.isGone
+import androidx.core.view.isVisible
+import androidx.fragment.app.FragmentActivity
+import androidx.navigation.findNavController
+import code.name.monkey.retromusic.EXTRA_ALBUM_ID
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
+import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
+import code.name.monkey.retromusic.glide.RetroGlideExtension
+import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
+import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
+import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
+import code.name.monkey.retromusic.helper.MusicPlayerRemote
+import code.name.monkey.retromusic.helper.SortOrder
+import code.name.monkey.retromusic.helper.menu.SongMenuHelper
+import code.name.monkey.retromusic.helper.menu.SongsMenuHelper
+import code.name.monkey.retromusic.model.Song
+import code.name.monkey.retromusic.util.MusicUtil
+import code.name.monkey.retromusic.util.PreferenceUtil
+import code.name.monkey.retromusic.util.RetroUtil
+import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
+import com.bumptech.glide.Glide
+import me.zhanghai.android.fastscroll.PopupTextProvider
+
+/**
+ * Created by hemanths on 13/08/17.
+ */
+
+open class SongAdapter(
+ override val activity: FragmentActivity,
+ var dataSet: MutableList,
+ protected var itemLayoutRes: Int,
+ showSectionName: Boolean = true
+) : AbsMultiSelectAdapter(
+ activity,
+ R.menu.menu_media_selection
+), PopupTextProvider {
+
+ private var showSectionName = true
+
+ init {
+ this.showSectionName = showSectionName
+ this.setHasStableIds(true)
+ }
+
+ open fun swapDataSet(dataSet: List) {
+ this.dataSet = ArrayList(dataSet)
+ notifyDataSetChanged()
+ }
+
+ override fun getItemId(position: Int): Long {
+ return dataSet[position].id
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val view =
+ try {
+ LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false)
+ } catch (e: Resources.NotFoundException) {
+ LayoutInflater.from(activity).inflate(R.layout.item_list, parent, false)
+ }
+ return createViewHolder(view)
+ }
+
+ protected open fun createViewHolder(view: View): ViewHolder {
+ return ViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val song = dataSet[position]
+ val isChecked = isChecked(song)
+ holder.itemView.isActivated = isChecked
+ holder.menu?.isGone = isChecked
+ holder.title?.text = getSongTitle(song)
+ holder.text?.text = getSongText(song)
+ holder.text2?.text = getSongText(song)
+ loadAlbumCover(song, holder)
+ val landscape = RetroUtil.isLandscape
+ if ((PreferenceUtil.songGridSize > 2 && !landscape) || (PreferenceUtil.songGridSizeLand > 5 && landscape)) {
+ holder.menu?.isVisible = false
+ }
+ }
+
+ private fun setColors(color: MediaNotificationProcessor, holder: ViewHolder) {
+ if (holder.paletteColorContainer != null) {
+ holder.title?.setTextColor(color.primaryTextColor)
+ holder.text?.setTextColor(color.secondaryTextColor)
+ holder.paletteColorContainer?.setBackgroundColor(color.backgroundColor)
+ holder.menu?.imageTintList = ColorStateList.valueOf(color.primaryTextColor)
+ }
+ holder.mask?.backgroundTintList = ColorStateList.valueOf(color.primaryTextColor)
+ }
+
+ protected open fun loadAlbumCover(song: Song, holder: ViewHolder) {
+ if (holder.image == null) {
+ return
+ }
+ Glide.with(activity)
+ .asBitmapPalette()
+ .songCoverOptions(song)
+ .load(RetroGlideExtension.getSongModel(song))
+ .into(object : RetroMusicColoredTarget(holder.image!!) {
+ override fun onColorReady(colors: MediaNotificationProcessor) {
+ setColors(colors, holder)
+ }
+ })
+ }
+
+ private fun getSongTitle(song: Song): String {
+ return song.title
+ }
+
+ private fun getSongText(song: Song): String {
+ return song.artistName
+ }
+
+ private fun getSongText2(song: Song): String {
+ return song.albumName
+ }
+
+ override fun getItemCount(): Int {
+ return dataSet.size
+ }
+
+ override fun getIdentifier(position: Int): Song? {
+ return dataSet[position]
+ }
+
+ override fun getName(model: Song): String {
+ return model.title
+ }
+
+ override fun onMultipleItemAction(menuItem: MenuItem, selection: List) {
+ SongsMenuHelper.handleMenuClick(activity, selection, menuItem.itemId)
+ }
+
+ override fun getPopupText(position: Int): String {
+ val sectionName: String? = when (PreferenceUtil.songSortOrder) {
+ SortOrder.SongSortOrder.SONG_DEFAULT -> return MusicUtil.getSectionName(
+ dataSet[position].title,
+ true
+ )
+
+ SortOrder.SongSortOrder.SONG_A_Z, SortOrder.SongSortOrder.SONG_Z_A -> dataSet[position].title
+ SortOrder.SongSortOrder.SONG_ALBUM -> dataSet[position].albumName
+ SortOrder.SongSortOrder.SONG_ARTIST -> dataSet[position].artistName
+ SortOrder.SongSortOrder.SONG_YEAR -> return MusicUtil.getYearString(dataSet[position].year)
+ SortOrder.SongSortOrder.COMPOSER -> dataSet[position].composer
+ SortOrder.SongSortOrder.SONG_ALBUM_ARTIST -> dataSet[position].albumArtist
+ else -> {
+ return ""
+ }
+ }
+ return MusicUtil.getSectionName(sectionName)
+ }
+
+ open inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
+ protected open var songMenuRes = SongMenuHelper.MENU_RES
+ protected open val song: Song
+ get() = dataSet[layoutPosition]
+
+ init {
+ menu?.setOnClickListener(object : SongMenuHelper.OnClickSongMenu(activity) {
+ override val song: Song
+ get() = this@ViewHolder.song
+
+ override val menuRes: Int
+ get() = songMenuRes
+
+ override fun onMenuItemClick(item: MenuItem): Boolean {
+ return onSongMenuItemClick(item) || super.onMenuItemClick(item)
+ }
+ })
+ }
+
+ protected open fun onSongMenuItemClick(item: MenuItem): Boolean {
+ if (image != null && image!!.isVisible) {
+ when (item.itemId) {
+ R.id.action_go_to_album -> {
+ activity.findNavController(R.id.fragment_container)
+ .navigate(
+ R.id.albumDetailsFragment,
+ bundleOf(EXTRA_ALBUM_ID to song.albumId)
+ )
+ return true
+ }
+ }
+ }
+ return false
+ }
+
+ override fun onClick(v: View?) {
+ if (isInQuickSelectMode) {
+ toggleChecked(layoutPosition)
+ } else {
+ MusicPlayerRemote.openQueue(dataSet, layoutPosition, true)
+ }
+ }
+
+ override fun onLongClick(v: View?): Boolean {
+ println("Long click")
+ return toggleChecked(layoutPosition)
+ }
+ }
+
+ companion object {
+ val TAG: String = SongAdapter::class.java.simpleName
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutIconGenerator.java b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutIconGenerator.java
deleted file mode 100644
index 7c9eef617..000000000
--- a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutIconGenerator.java
+++ /dev/null
@@ -1,71 +0,0 @@
-package code.name.monkey.retromusic.appshortcuts;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.graphics.drawable.LayerDrawable;
-import android.os.Build;
-import android.support.annotation.RequiresApi;
-import android.util.TypedValue;
-
-import code.name.monkey.appthemehelper.ThemeStore;
-
-import code.name.monkey.retromusic.R;
-import code.name.monkey.retromusic.util.PreferenceUtil;
-import code.name.monkey.retromusic.util.RetroUtil;
-
-/**
- * @author Adrian Campos
- */
-@RequiresApi(Build.VERSION_CODES.N_MR1)
-public final class AppShortcutIconGenerator {
- public static Icon generateThemedIcon(Context context, int iconId) {
- if (PreferenceUtil.getInstance(context).coloredAppShortcuts()){
- return generateUserThemedIcon(context, iconId);
- } else {
- return generateDefaultThemedIcon(context, iconId);
- }
- }
-
- private static Icon generateDefaultThemedIcon(Context context, int iconId) {
- // Return an Icon of iconId with default colors
- return generateThemedIcon(context, iconId,
- context.getColor(R.color.app_shortcut_default_foreground),
- context.getColor(R.color.app_shortcut_default_background)
- );
- }
-
- private static Icon generateUserThemedIcon(Context context, int iconId) {
- // Get background color from context's theme
- final TypedValue typedColorBackground = new TypedValue();
- context.getTheme().resolveAttribute(android.R.attr.colorBackground, typedColorBackground, true);
-
- // Return an Icon of iconId with those colors
- return generateThemedIcon(context, iconId,
- ThemeStore.accentColor(context),
- typedColorBackground.data
- );
- }
-
- private static Icon generateThemedIcon(Context context, int iconId, int foregroundColor, int backgroundColor) {
- // Get and tint foreground and background drawables
- Drawable vectorDrawable = RetroUtil.getTintedVectorDrawable(context, iconId, foregroundColor);
- Drawable backgroundDrawable = RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_app_shortcut_background, backgroundColor);
-
- // Squash the two drawables together
- LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{backgroundDrawable, vectorDrawable});
-
- // Return as an Icon
- return Icon.createWithBitmap(drawableToBitmap(layerDrawable));
- }
-
- private static Bitmap drawableToBitmap(Drawable drawable) {
- Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(bitmap);
- drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
- drawable.draw(canvas);
- return bitmap;
- }
-}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutIconGenerator.kt b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutIconGenerator.kt
new file mode 100644
index 000000000..ad8f964e7
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutIconGenerator.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.appshortcuts
+
+import android.content.Context
+import android.graphics.drawable.Icon
+import android.graphics.drawable.LayerDrawable
+import android.os.Build
+import android.util.TypedValue
+import androidx.annotation.RequiresApi
+import androidx.core.graphics.drawable.toBitmap
+import code.name.monkey.appthemehelper.ThemeStore
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.extensions.getTintedDrawable
+import code.name.monkey.retromusic.util.PreferenceUtil
+
+@RequiresApi(Build.VERSION_CODES.N_MR1)
+object AppShortcutIconGenerator {
+ fun generateThemedIcon(context: Context, iconId: Int): Icon {
+ return if (PreferenceUtil.isColoredAppShortcuts) {
+ generateUserThemedIcon(context, iconId)
+ } else {
+ generateDefaultThemedIcon(context, iconId)
+ }
+ }
+
+ private fun generateDefaultThemedIcon(context: Context, iconId: Int): Icon {
+ // Return an Icon of iconId with default colors
+ return generateThemedIcon(
+ context,
+ iconId,
+ context.getColor(R.color.app_shortcut_default_foreground),
+ context.getColor(R.color.app_shortcut_default_background)
+ )
+ }
+
+ private fun generateUserThemedIcon(context: Context, iconId: Int): Icon {
+ // Get background color from context's theme
+ val typedColorBackground = TypedValue()
+ context.theme.resolveAttribute(android.R.attr.colorBackground, typedColorBackground, true)
+
+ // Return an Icon of iconId with those colors
+ return generateThemedIcon(
+ context, iconId, ThemeStore.accentColor(context), typedColorBackground.data
+ )
+ }
+
+ private fun generateThemedIcon(
+ context: Context,
+ iconId: Int,
+ foregroundColor: Int,
+ backgroundColor: Int,
+ ): Icon {
+ // Get and tint foreground and background drawables
+ val vectorDrawable = context.getTintedDrawable(iconId, foregroundColor)
+ val backgroundDrawable =
+ context.getTintedDrawable(R.drawable.ic_app_shortcut_background, backgroundColor)
+
+ // Squash the two drawables together
+ val layerDrawable = LayerDrawable(arrayOf(backgroundDrawable, vectorDrawable))
+
+ // Return as an Icon
+ return Icon.createWithBitmap(layerDrawable.toBitmap())
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutLauncherActivity.java b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutLauncherActivity.java
deleted file mode 100644
index 85e9c4021..000000000
--- a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutLauncherActivity.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package code.name.monkey.retromusic.appshortcuts;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-
-import code.name.monkey.retromusic.model.Playlist;
-import code.name.monkey.retromusic.model.smartplaylist.LastAddedPlaylist;
-import code.name.monkey.retromusic.model.smartplaylist.MyTopTracksPlaylist;
-import code.name.monkey.retromusic.model.smartplaylist.ShuffleAllPlaylist;
-
-import code.name.monkey.retromusic.appshortcuts.shortcuttype.LastAddedShortcutType;
-import code.name.monkey.retromusic.appshortcuts.shortcuttype.ShuffleAllShortcutType;
-import code.name.monkey.retromusic.appshortcuts.shortcuttype.TopTracksShortcutType;
-import code.name.monkey.retromusic.service.MusicService;
-
-import static code.name.monkey.retromusic.Constants.*;
-
-/**
- * @author Adrian Campos
- */
-
-public class AppShortcutLauncherActivity extends Activity {
- public static final String KEY_SHORTCUT_TYPE = "code.name.monkey.retromusic.appshortcuts.ShortcutType";
-
- public static final int SHORTCUT_TYPE_SHUFFLE_ALL = 0;
- public static final int SHORTCUT_TYPE_TOP_TRACKS = 1;
- public static final int SHORTCUT_TYPE_LAST_ADDED = 2;
- public static final int SHORTCUT_TYPE_NONE = 3;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- int shortcutType = SHORTCUT_TYPE_NONE;
-
- // Set shortcutType from the intent extras
- Bundle extras = getIntent().getExtras();
- if (extras != null) {
- //noinspection WrongConstant
- shortcutType = extras.getInt(KEY_SHORTCUT_TYPE, SHORTCUT_TYPE_NONE);
- }
-
- switch (shortcutType) {
- case SHORTCUT_TYPE_SHUFFLE_ALL:
- startServiceWithPlaylist(MusicService.SHUFFLE_MODE_SHUFFLE,
- new ShuffleAllPlaylist(getApplicationContext()));
- DynamicShortcutManager.reportShortcutUsed(this, ShuffleAllShortcutType.getId());
- break;
- case SHORTCUT_TYPE_TOP_TRACKS:
- startServiceWithPlaylist(MusicService.SHUFFLE_MODE_NONE,
- new MyTopTracksPlaylist(getApplicationContext()));
- DynamicShortcutManager.reportShortcutUsed(this, TopTracksShortcutType.getId());
- break;
- case SHORTCUT_TYPE_LAST_ADDED:
- startServiceWithPlaylist(MusicService.SHUFFLE_MODE_NONE,
- new LastAddedPlaylist(getApplicationContext()));
- DynamicShortcutManager.reportShortcutUsed(this, LastAddedShortcutType.getId());
- break;
- }
-
- finish();
- }
-
- private void startServiceWithPlaylist(int shuffleMode, Playlist playlist) {
- Intent intent = new Intent(this, MusicService.class);
- intent.setAction(ACTION_PLAY_PLAYLIST);
-
- Bundle bundle = new Bundle();
- bundle.putParcelable(INTENT_EXTRA_PLAYLIST, playlist);
- bundle.putInt(INTENT_EXTRA_SHUFFLE_MODE, shuffleMode);
-
- intent.putExtras(bundle);
-
- startService(intent);
- }
-}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutLauncherActivity.kt b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutLauncherActivity.kt
new file mode 100644
index 000000000..9f10d795b
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutLauncherActivity.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.appshortcuts
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import androidx.core.os.bundleOf
+import code.name.monkey.retromusic.appshortcuts.shortcuttype.LastAddedShortcutType
+import code.name.monkey.retromusic.appshortcuts.shortcuttype.ShuffleAllShortcutType
+import code.name.monkey.retromusic.appshortcuts.shortcuttype.TopTracksShortcutType
+import code.name.monkey.retromusic.extensions.extraNotNull
+import code.name.monkey.retromusic.model.Playlist
+import code.name.monkey.retromusic.model.smartplaylist.LastAddedPlaylist
+import code.name.monkey.retromusic.model.smartplaylist.ShuffleAllPlaylist
+import code.name.monkey.retromusic.model.smartplaylist.TopTracksPlaylist
+import code.name.monkey.retromusic.service.MusicService
+import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_PLAY_PLAYLIST
+import code.name.monkey.retromusic.service.MusicService.Companion.INTENT_EXTRA_PLAYLIST
+import code.name.monkey.retromusic.service.MusicService.Companion.INTENT_EXTRA_SHUFFLE_MODE
+import code.name.monkey.retromusic.service.MusicService.Companion.SHUFFLE_MODE_NONE
+import code.name.monkey.retromusic.service.MusicService.Companion.SHUFFLE_MODE_SHUFFLE
+
+class AppShortcutLauncherActivity : Activity() {
+
+ public override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ when (extraNotNull(KEY_SHORTCUT_TYPE, SHORTCUT_TYPE_NONE).value) {
+ SHORTCUT_TYPE_SHUFFLE_ALL -> {
+ startServiceWithPlaylist(
+ SHUFFLE_MODE_SHUFFLE, ShuffleAllPlaylist()
+ )
+ DynamicShortcutManager.reportShortcutUsed(this, ShuffleAllShortcutType.id)
+ }
+ SHORTCUT_TYPE_TOP_TRACKS -> {
+ startServiceWithPlaylist(
+ SHUFFLE_MODE_NONE, TopTracksPlaylist()
+ )
+ DynamicShortcutManager.reportShortcutUsed(this, TopTracksShortcutType.id)
+ }
+ SHORTCUT_TYPE_LAST_ADDED -> {
+ startServiceWithPlaylist(
+ SHUFFLE_MODE_NONE, LastAddedPlaylist()
+ )
+ DynamicShortcutManager.reportShortcutUsed(this, LastAddedShortcutType.id)
+ }
+ }
+ finish()
+ }
+
+ private fun startServiceWithPlaylist(shuffleMode: Int, playlist: Playlist) {
+ val intent = Intent(this, MusicService::class.java)
+ intent.action = ACTION_PLAY_PLAYLIST
+
+ val bundle = bundleOf(
+ INTENT_EXTRA_PLAYLIST to playlist,
+ INTENT_EXTRA_SHUFFLE_MODE to shuffleMode
+ )
+
+ intent.putExtras(bundle)
+
+ startService(intent)
+ }
+
+ companion object {
+ const val KEY_SHORTCUT_TYPE = "io.github.muntashirakon.Music.appshortcuts.ShortcutType"
+ const val SHORTCUT_TYPE_SHUFFLE_ALL = 0L
+ const val SHORTCUT_TYPE_TOP_TRACKS = 1L
+ const val SHORTCUT_TYPE_LAST_ADDED = 2L
+ const val SHORTCUT_TYPE_NONE = 4L
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/DynamicShortcutManager.java b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/DynamicShortcutManager.java
deleted file mode 100644
index 5fb280549..000000000
--- a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/DynamicShortcutManager.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package code.name.monkey.retromusic.appshortcuts;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ShortcutInfo;
-import android.content.pm.ShortcutManager;
-import android.graphics.drawable.Icon;
-import android.os.Build;
-
-import code.name.monkey.retromusic.appshortcuts.shortcuttype.LastAddedShortcutType;
-import code.name.monkey.retromusic.appshortcuts.shortcuttype.ShuffleAllShortcutType;
-import code.name.monkey.retromusic.appshortcuts.shortcuttype.TopTracksShortcutType;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * @author Adrian Campos
- */
-
-@TargetApi(Build.VERSION_CODES.N_MR1)
-public class DynamicShortcutManager {
-
- private Context context;
- private ShortcutManager shortcutManager;
-
- public DynamicShortcutManager(Context context) {
- this.context = context;
- shortcutManager = this.context.getSystemService(ShortcutManager.class);
- }
-
- public static ShortcutInfo createShortcut(Context context, String id, String shortLabel, String longLabel, Icon icon, Intent intent) {
- return new ShortcutInfo.Builder(context, id)
- .setShortLabel(shortLabel)
- .setLongLabel(longLabel)
- .setIcon(icon)
- .setIntent(intent)
- .build();
- }
-
- public void initDynamicShortcuts() {
- if (shortcutManager.getDynamicShortcuts().size() == 0) {
- shortcutManager.setDynamicShortcuts(getDefaultShortcuts());
- }
- }
-
- public void updateDynamicShortcuts() {
- shortcutManager.updateShortcuts(getDefaultShortcuts());
- }
-
- public List getDefaultShortcuts() {
- return (Arrays.asList(
- new ShuffleAllShortcutType(context).getShortcutInfo(),
- new TopTracksShortcutType(context).getShortcutInfo(),
- new LastAddedShortcutType(context).getShortcutInfo()
- ));
- }
-
- public static void reportShortcutUsed(Context context, String shortcutId){
- context.getSystemService(ShortcutManager.class).reportShortcutUsed(shortcutId);
- }
-}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/DynamicShortcutManager.kt b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/DynamicShortcutManager.kt
new file mode 100644
index 000000000..a943ed0c8
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/DynamicShortcutManager.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.appshortcuts
+
+import android.annotation.TargetApi
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ShortcutInfo
+import android.content.pm.ShortcutManager
+import android.graphics.drawable.Icon
+import android.os.Build
+import androidx.core.content.getSystemService
+import code.name.monkey.retromusic.appshortcuts.shortcuttype.LastAddedShortcutType
+import code.name.monkey.retromusic.appshortcuts.shortcuttype.ShuffleAllShortcutType
+import code.name.monkey.retromusic.appshortcuts.shortcuttype.TopTracksShortcutType
+
+@TargetApi(Build.VERSION_CODES.N_MR1)
+class DynamicShortcutManager(private val context: Context) {
+ private val shortcutManager: ShortcutManager? =
+ this.context.getSystemService()
+
+ private val defaultShortcuts: List
+ get() = listOf(
+ ShuffleAllShortcutType(context).shortcutInfo,
+ TopTracksShortcutType(context).shortcutInfo,
+ LastAddedShortcutType(context).shortcutInfo
+ )
+
+ fun initDynamicShortcuts() {
+ // if (shortcutManager.dynamicShortcuts.size == 0) {
+ shortcutManager?.dynamicShortcuts = defaultShortcuts
+ // }
+ }
+
+ fun updateDynamicShortcuts() {
+ shortcutManager?.updateShortcuts(defaultShortcuts)
+ }
+
+ companion object {
+
+ fun createShortcut(
+ context: Context,
+ id: String,
+ shortLabel: String,
+ longLabel: String,
+ icon: Icon,
+ intent: Intent
+ ): ShortcutInfo {
+ return ShortcutInfo.Builder(context, id)
+ .setShortLabel(shortLabel)
+ .setLongLabel(longLabel)
+ .setIcon(icon)
+ .setIntent(intent)
+ .build()
+ }
+
+ fun reportShortcutUsed(context: Context, shortcutId: String) {
+ context.getSystemService()?.reportShortcutUsed(shortcutId)
+ }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/BaseShortcutType.java b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/BaseShortcutType.java
deleted file mode 100644
index 28f16a7e4..000000000
--- a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/BaseShortcutType.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package code.name.monkey.retromusic.appshortcuts.shortcuttype;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ShortcutInfo;
-import android.os.Build;
-import android.os.Bundle;
-
-import code.name.monkey.retromusic.appshortcuts.AppShortcutLauncherActivity;
-
-
-/**
- * @author Adrian Campos
- */
-@TargetApi(Build.VERSION_CODES.N_MR1)
-public abstract class BaseShortcutType {
-
- static final String ID_PREFIX = "code.name.monkey.retromusic.appshortcuts.id.";
-
- Context context;
-
- public BaseShortcutType(Context context) {
- this.context = context;
- }
-
- static public String getId() {
- return ID_PREFIX + "invalid";
- }
-
- abstract ShortcutInfo getShortcutInfo();
-
- /**
- * Creates an Intent that will launch MainActivtiy and immediately play {@param songs} in either shuffle or normal mode
- *
- * @param shortcutType Describes the type of shortcut to create (ShuffleAll, TopTracks, custom playlist, etc.)
- * @return
- */
- Intent getPlaySongsIntent(int shortcutType) {
- Intent intent = new Intent(context, AppShortcutLauncherActivity.class);
- intent.setAction(Intent.ACTION_VIEW);
-
- Bundle b = new Bundle();
- b.putInt(AppShortcutLauncherActivity.KEY_SHORTCUT_TYPE, shortcutType);
-
- intent.putExtras(b);
-
- return intent;
- }
-}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/BaseShortcutType.kt b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/BaseShortcutType.kt
new file mode 100644
index 000000000..4b0c7f3a0
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/BaseShortcutType.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.appshortcuts.shortcuttype
+
+import android.annotation.TargetApi
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ShortcutInfo
+import android.os.Build
+import androidx.core.os.bundleOf
+import code.name.monkey.retromusic.appshortcuts.AppShortcutLauncherActivity
+
+@TargetApi(Build.VERSION_CODES.N_MR1)
+abstract class BaseShortcutType(internal var context: Context) {
+
+ internal abstract val shortcutInfo: ShortcutInfo
+
+ /**
+ * Creates an Intent that will launch MainActivtiy and immediately play {@param songs} in either shuffle or normal mode
+ *
+ * @param shortcutType Describes the type of shortcut to create (ShuffleAll, TopTracks, custom playlist, etc.)
+ * @return
+ */
+ internal fun getPlaySongsIntent(shortcutType: Long): Intent {
+ val intent = Intent(context, AppShortcutLauncherActivity::class.java)
+ intent.action = Intent.ACTION_VIEW
+ val b = bundleOf(AppShortcutLauncherActivity.KEY_SHORTCUT_TYPE to shortcutType)
+ intent.putExtras(b)
+ return intent
+ }
+
+ companion object {
+ internal const val ID_PREFIX = "io.github.muntashirakon.Music.appshortcuts.id."
+ val id: String
+ get() = ID_PREFIX + "invalid"
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/LastAddedShortcutType.java b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/LastAddedShortcutType.java
deleted file mode 100644
index 72db3eeb8..000000000
--- a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/LastAddedShortcutType.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package code.name.monkey.retromusic.appshortcuts.shortcuttype;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.pm.ShortcutInfo;
-import android.os.Build;
-
-import code.name.monkey.retromusic.R;
-import code.name.monkey.retromusic.appshortcuts.AppShortcutIconGenerator;
-import code.name.monkey.retromusic.appshortcuts.AppShortcutLauncherActivity;
-
-
-/**
- * @author Adrian Campos
- */
-@TargetApi(Build.VERSION_CODES.N_MR1)
-public final class LastAddedShortcutType extends BaseShortcutType {
- public LastAddedShortcutType(Context context) {
- super(context);
- }
-
- public static String getId() {
- return ID_PREFIX + "last_added";
- }
-
- public ShortcutInfo getShortcutInfo() {
- return new ShortcutInfo.Builder(context, getId())
- .setShortLabel(context.getString(R.string.app_shortcut_last_added_short))
- .setLongLabel(context.getString(R.string.app_shortcut_last_added_long))
- .setIcon(AppShortcutIconGenerator.generateThemedIcon(context, R.drawable.ic_app_shortcut_last_added))
- .setIntent(getPlaySongsIntent(AppShortcutLauncherActivity.SHORTCUT_TYPE_LAST_ADDED))
- .build();
- }
-}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/LastAddedShortcutType.kt b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/LastAddedShortcutType.kt
new file mode 100644
index 000000000..d0976042c
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/LastAddedShortcutType.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.appshortcuts.shortcuttype
+
+import android.annotation.TargetApi
+import android.content.Context
+import android.content.pm.ShortcutInfo
+import android.os.Build
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.appshortcuts.AppShortcutIconGenerator
+import code.name.monkey.retromusic.appshortcuts.AppShortcutLauncherActivity
+
+@TargetApi(Build.VERSION_CODES.N_MR1)
+class LastAddedShortcutType(context: Context) : BaseShortcutType(context) {
+
+ override val shortcutInfo: ShortcutInfo
+ get() = ShortcutInfo.Builder(
+ context,
+ id
+ ).setShortLabel(context.getString(R.string.app_shortcut_last_added_short)).setLongLabel(
+ context.getString(R.string.app_shortcut_last_added_long)
+ ).setIcon(
+ AppShortcutIconGenerator.generateThemedIcon(
+ context,
+ R.drawable.ic_app_shortcut_last_added
+ )
+ ).setIntent(getPlaySongsIntent(AppShortcutLauncherActivity.SHORTCUT_TYPE_LAST_ADDED))
+ .build()
+
+ companion object {
+
+ val id: String
+ get() = ID_PREFIX + "last_added"
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/ShuffleAllShortcutType.java b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/ShuffleAllShortcutType.java
deleted file mode 100644
index be3ba0088..000000000
--- a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/ShuffleAllShortcutType.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package code.name.monkey.retromusic.appshortcuts.shortcuttype;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.pm.ShortcutInfo;
-import android.os.Build;
-
-import code.name.monkey.retromusic.R;
-import code.name.monkey.retromusic.appshortcuts.AppShortcutIconGenerator;
-import code.name.monkey.retromusic.appshortcuts.AppShortcutLauncherActivity;
-
-
-
-/**
- * @author Adrian Campos
- */
-@TargetApi(Build.VERSION_CODES.N_MR1)
-public final class ShuffleAllShortcutType extends BaseShortcutType {
- public ShuffleAllShortcutType(Context context) {
- super(context);
- }
-
- public static String getId() {
- return ID_PREFIX + "shuffle_all";
- }
-
- public ShortcutInfo getShortcutInfo() {
- return new ShortcutInfo.Builder(context, getId())
- .setShortLabel(context.getString(R.string.app_shortcut_shuffle_all_short))
- .setLongLabel(context.getString(R.string.app_shortcut_shuffle_all_long))
- .setIcon(AppShortcutIconGenerator.generateThemedIcon(context, R.drawable.ic_app_shortcut_shuffle_all))
- .setIntent(getPlaySongsIntent(AppShortcutLauncherActivity.SHORTCUT_TYPE_SHUFFLE_ALL))
- .build();
- }
-}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/ShuffleAllShortcutType.kt b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/ShuffleAllShortcutType.kt
new file mode 100644
index 000000000..21855cacd
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/ShuffleAllShortcutType.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.appshortcuts.shortcuttype
+
+import android.annotation.TargetApi
+import android.content.Context
+import android.content.pm.ShortcutInfo
+import android.os.Build
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.appshortcuts.AppShortcutIconGenerator
+import code.name.monkey.retromusic.appshortcuts.AppShortcutLauncherActivity
+
+@TargetApi(Build.VERSION_CODES.N_MR1)
+class ShuffleAllShortcutType(context: Context) : BaseShortcutType(context) {
+
+ override val shortcutInfo: ShortcutInfo
+ get() = ShortcutInfo.Builder(context, id)
+ .setShortLabel(context.getString(R.string.app_shortcut_shuffle_all_short))
+ .setLongLabel(context.getString(R.string.app_shortcut_shuffle_all_long))
+ .setIcon(AppShortcutIconGenerator.generateThemedIcon(context, R.drawable.ic_app_shortcut_shuffle_all))
+ .setIntent(getPlaySongsIntent(AppShortcutLauncherActivity.SHORTCUT_TYPE_SHUFFLE_ALL))
+ .build()
+
+ companion object {
+
+ val id: String
+ get() = ID_PREFIX + "shuffle_all"
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/TopTracksShortcutType.java b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/TopTracksShortcutType.java
deleted file mode 100644
index 3e78db33c..000000000
--- a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/TopTracksShortcutType.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package code.name.monkey.retromusic.appshortcuts.shortcuttype;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.pm.ShortcutInfo;
-import android.os.Build;
-
-import code.name.monkey.retromusic.R;
-import code.name.monkey.retromusic.appshortcuts.AppShortcutIconGenerator;
-import code.name.monkey.retromusic.appshortcuts.AppShortcutLauncherActivity;
-
-
-
-/**
- * @author Adrian Campos
- */
-@TargetApi(Build.VERSION_CODES.N_MR1)
-public final class TopTracksShortcutType extends BaseShortcutType {
- public TopTracksShortcutType(Context context) {
- super(context);
- }
-
- public static String getId() {
- return ID_PREFIX + "top_tracks";
- }
-
- public ShortcutInfo getShortcutInfo() {
- return new ShortcutInfo.Builder(context, getId())
- .setShortLabel(context.getString(R.string.app_shortcut_top_tracks_short))
- .setLongLabel(context.getString(R.string.app_shortcut_top_tracks_long))
- .setIcon(AppShortcutIconGenerator.generateThemedIcon(context, R.drawable.ic_app_shortcut_top_tracks))
- .setIntent(getPlaySongsIntent(AppShortcutLauncherActivity.SHORTCUT_TYPE_TOP_TRACKS))
- .build();
- }
-}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/TopTracksShortcutType.kt b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/TopTracksShortcutType.kt
new file mode 100644
index 000000000..fc0fa9f47
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/TopTracksShortcutType.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ */
+package code.name.monkey.retromusic.appshortcuts.shortcuttype
+
+import android.annotation.TargetApi
+import android.content.Context
+import android.content.pm.ShortcutInfo
+import android.os.Build
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.appshortcuts.AppShortcutIconGenerator
+import code.name.monkey.retromusic.appshortcuts.AppShortcutLauncherActivity
+
+@TargetApi(Build.VERSION_CODES.N_MR1)
+class TopTracksShortcutType(context: Context) : BaseShortcutType(context) {
+
+ override val shortcutInfo: ShortcutInfo
+ get() = ShortcutInfo.Builder(
+ context, id
+ ).setShortLabel(context.getString(R.string.app_shortcut_top_tracks_short)).setLongLabel(
+ context.getString(R.string.app_shortcut_top_tracks_long)
+ ).setIcon(
+ AppShortcutIconGenerator.generateThemedIcon(
+ context, R.drawable.ic_app_shortcut_top_tracks
+ )
+ ).setIntent(getPlaySongsIntent(AppShortcutLauncherActivity.SHORTCUT_TYPE_TOP_TRACKS))
+ .build()
+
+ companion object {
+
+ val id: String
+ get() = ID_PREFIX + "top_tracks"
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.java b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.java
deleted file mode 100644
index 4b90e941f..000000000
--- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.java
+++ /dev/null
@@ -1,171 +0,0 @@
-package code.name.monkey.retromusic.appwidgets;
-
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.Point;
-import android.graphics.drawable.Drawable;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-import android.view.View;
-import android.widget.RemoteViews;
-import code.name.monkey.appthemehelper.util.MaterialValueHelper;
-import code.name.monkey.retromusic.Constants;
-import code.name.monkey.retromusic.R;
-import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget;
-import code.name.monkey.retromusic.glide.SongGlideRequest;
-import code.name.monkey.retromusic.model.Song;
-import code.name.monkey.retromusic.service.MusicService;
-import code.name.monkey.retromusic.ui.activities.MainActivity;
-import code.name.monkey.retromusic.util.RetroUtil;
-import com.bumptech.glide.Glide;
-import com.bumptech.glide.request.animation.GlideAnimation;
-import com.bumptech.glide.request.target.SimpleTarget;
-import com.bumptech.glide.request.target.Target;
-
-public class AppWidgetBig extends BaseAppWidget {
-
- public static final String NAME = "app_widget_big";
-
- private static AppWidgetBig mInstance;
- private Target target; // for cancellation
-
- public static synchronized AppWidgetBig getInstance() {
- if (mInstance == null) {
- mInstance = new AppWidgetBig();
- }
- return mInstance;
- }
-
- /**
- * Initialize given widgets to default state, where we launch Music on default click and hide
- * actions if service not running.
- */
- protected void defaultAppWidget(final Context context, final int[] appWidgetIds) {
- final RemoteViews appWidgetView = new RemoteViews(context.getPackageName(),
- R.layout.app_widget_big);
-
- appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE);
- appWidgetView.setImageViewResource(R.id.image, R.drawable.default_album_art);
- appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(
- RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_next_white_24dp,
- MaterialValueHelper.getPrimaryTextColor(context, false)), 1f));
- appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(
- RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_previous_white_24dp,
- MaterialValueHelper.getPrimaryTextColor(context, false)), 1f));
- appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap(
- RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_play_arrow_white_24dp,
- MaterialValueHelper.getPrimaryTextColor(context, false)), 1f));
-
- linkButtons(context, appWidgetView);
- pushUpdate(context, appWidgetIds, appWidgetView);
- }
-
- /**
- * Update all active widget instances by pushing changes
- */
- public void performUpdate(final MusicService service, final int[] appWidgetIds) {
- final RemoteViews appWidgetView = new RemoteViews(service.getPackageName(),
- R.layout.app_widget_big);
-
- final boolean isPlaying = service.isPlaying();
- final Song song = service.getCurrentSong();
-
- // Set the titles and artwork
- if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
- appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE);
- } else {
- appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE);
- appWidgetView.setTextViewText(R.id.title, song.title);
- appWidgetView.setTextViewText(R.id.text, getSongArtistAndAlbum(song));
- }
-
- // Set correct drawable for pause state
- int playPauseRes =
- isPlaying ? R.drawable.ic_pause_white_24dp : R.drawable.ic_play_arrow_white_24dp;
- appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap(
- RetroUtil.getTintedVectorDrawable(service, playPauseRes,
- MaterialValueHelper.getPrimaryTextColor(service, false)), 1f));
-
- // Set prev/next button drawables
- appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(
- RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp,
- MaterialValueHelper.getPrimaryTextColor(service, false)), 1f));
- appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(
- RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp,
- MaterialValueHelper.getPrimaryTextColor(service, false)), 1f));
-
- // Link actions buttons to intents
- linkButtons(service, appWidgetView);
-
- // Load the album cover async and push the update on completion
- Point p = RetroUtil.getScreenSize(service);
- final int widgetImageSize = Math.min(p.x, p.y);
- final Context appContext = service.getApplicationContext();
- service.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- if (target != null) {
- Glide.clear(target);
- }
- target = SongGlideRequest.Builder.from(Glide.with(appContext), song)
- .checkIgnoreMediaStore(appContext)
- .asBitmap().build()
- .into(new SimpleTarget(widgetImageSize, widgetImageSize) {
- @Override
- public void onResourceReady(Bitmap resource,
- GlideAnimation super Bitmap> glideAnimation) {
- update(resource);
- }
-
- @Override
- public void onLoadFailed(Exception e, Drawable errorDrawable) {
- super.onLoadFailed(e, errorDrawable);
- update(null);
- }
-
- private void update(@Nullable Bitmap bitmap) {
- if (bitmap == null) {
- appWidgetView.setImageViewResource(R.id.image, R.drawable.default_album_art);
- } else {
- appWidgetView.setImageViewBitmap(R.id.image, bitmap);
- }
- pushUpdate(appContext, appWidgetIds, appWidgetView);
- }
- });
- }
- });
- }
-
- /**
- * Link up various button actions using {@link PendingIntent}.
- */
- private void linkButtons(final Context context, final RemoteViews views) {
- Intent action;
- PendingIntent pendingIntent;
-
- final ComponentName serviceName = new ComponentName(context, MusicService.class);
-
- // Home
- action = new Intent(context, MainActivity.class);
- action.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- pendingIntent = PendingIntent.getActivity(context, 0, action, 0);
- views.setOnClickPendingIntent(R.id.clickable_area, pendingIntent);
-
- // Previous track
- pendingIntent = buildPendingIntent(context, Constants.ACTION_REWIND, serviceName);
- views.setOnClickPendingIntent(R.id.button_prev, pendingIntent);
-
- // Play and pause
- pendingIntent = buildPendingIntent(context, Constants.ACTION_TOGGLE_PAUSE, serviceName);
- views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent);
-
- // Next track
- pendingIntent = buildPendingIntent(context, Constants.ACTION_SKIP, serviceName);
- views.setOnClickPendingIntent(R.id.button_next, pendingIntent);
-
-
- }
-}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.kt
new file mode 100644
index 000000000..7d9497209
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.kt
@@ -0,0 +1,236 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.appwidgets
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.view.View
+import android.widget.RemoteViews
+import androidx.core.graphics.drawable.toBitmap
+import code.name.monkey.appthemehelper.util.MaterialValueHelper
+import code.name.monkey.appthemehelper.util.VersionUtils
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.activities.MainActivity
+import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
+import code.name.monkey.retromusic.extensions.getTintedDrawable
+import code.name.monkey.retromusic.glide.RetroGlideExtension
+import code.name.monkey.retromusic.service.MusicService
+import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_REWIND
+import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP
+import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
+import code.name.monkey.retromusic.util.PreferenceUtil
+import code.name.monkey.retromusic.util.RetroUtil
+import com.bumptech.glide.Glide
+import com.bumptech.glide.request.target.CustomTarget
+import com.bumptech.glide.request.target.Target
+import com.bumptech.glide.request.transition.Transition
+
+class AppWidgetBig : BaseAppWidget() {
+ private var target: Target? = null // for cancellation
+
+ /**
+ * Initialize given widgets to default state, where we launch Music on default click and hide
+ * actions if service not running.
+ */
+ override fun defaultAppWidget(context: Context, appWidgetIds: IntArray) {
+ val appWidgetView = RemoteViews(
+ context.packageName, R.layout.app_widget_big
+ )
+
+ appWidgetView.setViewVisibility(
+ R.id.media_titles,
+ View.INVISIBLE
+ )
+ appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
+ appWidgetView.setImageViewBitmap(
+ R.id.button_next, context.getTintedDrawable(
+ R.drawable.ic_skip_next,
+ MaterialValueHelper.getPrimaryTextColor(context, false)
+ ).toBitmap()
+ )
+ appWidgetView.setImageViewBitmap(
+ R.id.button_prev,
+ context.getTintedDrawable(
+ R.drawable.ic_skip_previous,
+ MaterialValueHelper.getPrimaryTextColor(context, false)
+ ).toBitmap()
+ )
+ appWidgetView.setImageViewBitmap(
+ R.id.button_toggle_play_pause,
+ context.getTintedDrawable(
+ R.drawable.ic_play_arrow_white_32dp,
+ MaterialValueHelper.getPrimaryTextColor(context, false)
+ ).toBitmap()
+ )
+
+ linkButtons(context, appWidgetView)
+ pushUpdate(context, appWidgetIds, appWidgetView)
+ }
+
+ /**
+ * Update all active widget instances by pushing changes
+ */
+ override fun performUpdate(service: MusicService, appWidgetIds: IntArray?) {
+ val appWidgetView = RemoteViews(
+ service.packageName, R.layout.app_widget_big
+ )
+
+ val isPlaying = service.isPlaying
+ val song = service.currentSong
+
+ // Set the titles and artwork
+ if (song.title.isEmpty() && song.artistName.isEmpty()) {
+ appWidgetView.setViewVisibility(
+ R.id.media_titles,
+ View.INVISIBLE
+ )
+ } else {
+ appWidgetView.setViewVisibility(
+ R.id.media_titles,
+ View.VISIBLE
+ )
+ appWidgetView.setTextViewText(R.id.title, song.title)
+ appWidgetView.setTextViewText(
+ R.id.text,
+ getSongArtistAndAlbum(song)
+ )
+ }
+
+ val primaryColor = MaterialValueHelper.getPrimaryTextColor(service, false)
+ // Set correct drawable for pause state
+ val playPauseRes =
+ if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play_arrow_white_32dp
+ appWidgetView.setImageViewBitmap(
+ R.id.button_toggle_play_pause,
+ service.getTintedDrawable(
+ playPauseRes,
+ primaryColor
+ ).toBitmap()
+ )
+
+ // Set prev/next button drawables
+ appWidgetView.setImageViewBitmap(
+ R.id.button_next,
+ service.getTintedDrawable(
+ R.drawable.ic_skip_next,
+ primaryColor
+ ).toBitmap()
+ )
+ appWidgetView.setImageViewBitmap(
+ R.id.button_prev,
+ service.getTintedDrawable(
+ R.drawable.ic_skip_previous,
+ primaryColor
+ ).toBitmap()
+ )
+
+ // Link actions buttons to intents
+ linkButtons(service, appWidgetView)
+
+ // Load the album cover async and push the update on completion
+ val p = RetroUtil.getScreenSize(service)
+ val widgetImageSize = p.x.coerceAtMost(p.y)
+ val appContext = service.applicationContext
+ service.runOnUiThread {
+ if (target != null) {
+ Glide.with(service).clear(target)
+ }
+ target = Glide.with(appContext)
+ .asBitmap()
+ //.checkIgnoreMediaStore()
+ .load(RetroGlideExtension.getSongModel(song))
+ .into(object : CustomTarget(widgetImageSize, widgetImageSize) {
+ override fun onResourceReady(
+ resource: Bitmap,
+ transition: Transition?,
+ ) {
+ update(resource)
+ }
+
+ override fun onLoadFailed(errorDrawable: Drawable?) {
+ super.onLoadFailed(errorDrawable)
+ update(null)
+ }
+
+ override fun onLoadCleared(placeholder: Drawable?) {}
+
+ private fun update(bitmap: Bitmap?) {
+ if (bitmap == null) {
+ appWidgetView.setImageViewResource(
+ R.id.image,
+ R.drawable.default_audio_art
+ )
+ } else {
+ appWidgetView.setImageViewBitmap(R.id.image, bitmap)
+ }
+ pushUpdate(appContext, appWidgetIds, appWidgetView)
+ }
+ })
+ }
+ }
+
+ /**
+ * Link up various button actions using [PendingIntent].
+ */
+ private fun linkButtons(context: Context, views: RemoteViews) {
+ val action = Intent(context, MainActivity::class.java)
+ .putExtra(
+ MainActivity.EXPAND_PANEL,
+ PreferenceUtil.isExpandPanel
+ )
+
+ val serviceName = ComponentName(context, MusicService::class.java)
+
+ // Home
+ action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
+ var pendingIntent =
+ PendingIntent.getActivity(
+ context, 0, action, if (VersionUtils.hasMarshmallow())
+ PendingIntent.FLAG_IMMUTABLE
+ else 0
+ )
+ views.setOnClickPendingIntent(R.id.clickable_area, pendingIntent)
+
+ // Previous track
+ pendingIntent = buildPendingIntent(context, ACTION_REWIND, serviceName)
+ views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
+
+ // Play and pause
+ pendingIntent = buildPendingIntent(context, ACTION_TOGGLE_PAUSE, serviceName)
+ views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
+
+ // Next track
+ pendingIntent = buildPendingIntent(context, ACTION_SKIP, serviceName)
+ views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
+ }
+
+ companion object {
+
+ const val NAME: String = "app_widget_big"
+ private var mInstance: AppWidgetBig? = null
+
+ val instance: AppWidgetBig
+ @Synchronized get() {
+ if (mInstance == null) {
+ mInstance = AppWidgetBig()
+ }
+ return mInstance!!
+ }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.java b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.java
deleted file mode 100644
index d7c080672..000000000
--- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.java
+++ /dev/null
@@ -1,194 +0,0 @@
-package code.name.monkey.retromusic.appwidgets;
-
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-import android.support.annotation.Nullable;
-import android.support.v7.graphics.Palette;
-import android.text.TextUtils;
-import android.view.View;
-import android.widget.RemoteViews;
-import code.name.monkey.appthemehelper.util.MaterialValueHelper;
-import code.name.monkey.retromusic.Constants;
-import code.name.monkey.retromusic.R;
-import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget;
-import code.name.monkey.retromusic.glide.SongGlideRequest;
-import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper;
-import code.name.monkey.retromusic.model.Song;
-import code.name.monkey.retromusic.service.MusicService;
-import code.name.monkey.retromusic.ui.activities.MainActivity;
-import code.name.monkey.retromusic.util.RetroUtil;
-import com.bumptech.glide.Glide;
-import com.bumptech.glide.request.animation.GlideAnimation;
-import com.bumptech.glide.request.target.SimpleTarget;
-import com.bumptech.glide.request.target.Target;
-
-public class AppWidgetCard extends BaseAppWidget {
-
- public static final String NAME = "app_widget_card";
-
- private static AppWidgetCard mInstance;
- private static int imageSize = 0;
- private static float cardRadius = 0f;
- private Target target; // for cancellation
-
- public static synchronized AppWidgetCard getInstance() {
- if (mInstance == null) {
- mInstance = new AppWidgetCard();
- }
- return mInstance;
- }
-
- /**
- * Initialize given widgets to default state, where we launch Music on default click and hide
- * actions if service not running.
- */
- protected void defaultAppWidget(final Context context, final int[] appWidgetIds) {
- final RemoteViews appWidgetView = new RemoteViews(context.getPackageName(),
- R.layout.app_widget_card);
-
- appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE);
- appWidgetView.setImageViewResource(R.id.image, R.drawable.default_album_art);
- appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(
- RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_next_white_24dp,
- MaterialValueHelper.getSecondaryTextColor(context, true)), 1f));
- appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(
- RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_previous_white_24dp,
- MaterialValueHelper.getSecondaryTextColor(context, true)), 1f));
- appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap(
- RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_play_arrow_white_24dp,
- MaterialValueHelper.getSecondaryTextColor(context, true)), 1f));
-
- linkButtons(context, appWidgetView);
- pushUpdate(context, appWidgetIds, appWidgetView);
- }
-
- /**
- * Update all active widget instances by pushing changes
- */
- public void performUpdate(final MusicService service, final int[] appWidgetIds) {
- final RemoteViews appWidgetView = new RemoteViews(service.getPackageName(),
- R.layout.app_widget_card);
-
- final boolean isPlaying = service.isPlaying();
- final Song song = service.getCurrentSong();
-
- // Set the titles and artwork
- if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
- appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE);
- } else {
- appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE);
- appWidgetView.setTextViewText(R.id.title, song.title);
- appWidgetView.setTextViewText(R.id.text, getSongArtistAndAlbum(song));
- }
-
- // Set correct drawable for pause state
- int playPauseRes =
- isPlaying ? R.drawable.ic_pause_white_24dp : R.drawable.ic_play_arrow_white_24dp;
- appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap(
- RetroUtil.getTintedVectorDrawable(service, playPauseRes,
- MaterialValueHelper.getSecondaryTextColor(service, true)), 1f));
-
- // Set prev/next button drawables
- appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(
- RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp,
- MaterialValueHelper.getSecondaryTextColor(service, true)), 1f));
- appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(
- RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp,
- MaterialValueHelper.getSecondaryTextColor(service, true)), 1f));
-
- // Link actions buttons to intents
- linkButtons(service, appWidgetView);
-
- if (imageSize == 0) {
- imageSize = service.getResources().getDimensionPixelSize(R.dimen.app_widget_card_image_size);
- }
- if (cardRadius == 0f) {
- cardRadius = service.getResources().getDimension(R.dimen.app_widget_card_radius);
- }
-
- // Load the album cover async and push the update on completion
- service.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- if (target != null) {
- Glide.clear(target);
- }
- target = SongGlideRequest.Builder.from(Glide.with(service), song)
- .checkIgnoreMediaStore(service)
- .generatePalette(service).build()
- .centerCrop()
- .into(new SimpleTarget(imageSize, imageSize) {
- @Override
- public void onResourceReady(BitmapPaletteWrapper resource,
- GlideAnimation super BitmapPaletteWrapper> glideAnimation) {
- Palette palette = resource.getPalette();
- update(resource.getBitmap(), palette.getVibrantColor(palette
- .getMutedColor(MaterialValueHelper.getSecondaryTextColor(service, true))));
- }
-
- @Override
- public void onLoadFailed(Exception e, Drawable errorDrawable) {
- super.onLoadFailed(e, errorDrawable);
- update(null, MaterialValueHelper.getSecondaryTextColor(service, true));
- }
-
- private void update(@Nullable Bitmap bitmap, int color) {
- // Set correct drawable for pause state
- int playPauseRes = isPlaying ? R.drawable.ic_pause_white_24dp
- : R.drawable.ic_play_arrow_white_24dp;
- appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause,
- createBitmap(RetroUtil.getTintedVectorDrawable(service, playPauseRes, color), 1f));
-
- // Set prev/next button drawables
- appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(
- RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp,
- color), 1f));
- appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(
- RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp,
- color), 1f));
-
- final Drawable image = getAlbumArtDrawable(service.getResources(), bitmap);
- final Bitmap roundedBitmap = createRoundedBitmap(image, imageSize, imageSize,
- cardRadius, 0, cardRadius, 0);
- appWidgetView.setImageViewBitmap(R.id.image, roundedBitmap);
-
- pushUpdate(service, appWidgetIds, appWidgetView);
- }
- });
- }
- });
- }
-
- /**
- * Link up various button actions using {@link PendingIntent}.
- */
- private void linkButtons(final Context context, final RemoteViews views) {
- Intent action;
- PendingIntent pendingIntent;
-
- final ComponentName serviceName = new ComponentName(context, MusicService.class);
-
- // Home
- action = new Intent(context, MainActivity.class);
- action.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- pendingIntent = PendingIntent.getActivity(context, 0, action, 0);
- views.setOnClickPendingIntent(R.id.image, pendingIntent);
- views.setOnClickPendingIntent(R.id.media_titles, pendingIntent);
-
- // Previous track
- pendingIntent = buildPendingIntent(context, Constants.ACTION_REWIND, serviceName);
- views.setOnClickPendingIntent(R.id.button_prev, pendingIntent);
-
- // Play and pause
- pendingIntent = buildPendingIntent(context, Constants.ACTION_TOGGLE_PAUSE, serviceName);
- views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent);
-
- // Next track
- pendingIntent = buildPendingIntent(context, Constants.ACTION_SKIP, serviceName);
- views.setOnClickPendingIntent(R.id.button_next, pendingIntent);
- }
-}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.kt
new file mode 100644
index 000000000..471d908d6
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.kt
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.appwidgets
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.view.View
+import android.widget.RemoteViews
+import androidx.core.graphics.drawable.toBitmap
+import code.name.monkey.appthemehelper.util.MaterialValueHelper
+import code.name.monkey.appthemehelper.util.VersionUtils
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.activities.MainActivity
+import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
+import code.name.monkey.retromusic.extensions.getTintedDrawable
+import code.name.monkey.retromusic.glide.RetroGlideExtension
+import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
+import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
+import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
+import code.name.monkey.retromusic.service.MusicService
+import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_REWIND
+import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP
+import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
+import code.name.monkey.retromusic.util.PreferenceUtil
+import com.bumptech.glide.Glide
+import com.bumptech.glide.request.target.CustomTarget
+import com.bumptech.glide.request.target.Target
+import com.bumptech.glide.request.transition.Transition
+
+class AppWidgetCard : BaseAppWidget() {
+ private var target: Target? = null // for cancellation
+
+ /**
+ * Initialize given widgets to default state, where we launch Music on default click and hide
+ * actions if service not running.
+ */
+ override fun defaultAppWidget(context: Context, appWidgetIds: IntArray) {
+ val appWidgetView = RemoteViews(context.packageName, R.layout.app_widget_card)
+
+ appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
+ appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
+ val secondaryColor = MaterialValueHelper.getSecondaryTextColor(context, true)
+ appWidgetView.setImageViewBitmap(
+ R.id.button_next,
+ context.getTintedDrawable(
+ R.drawable.ic_skip_next,
+ secondaryColor
+ ).toBitmap()
+ )
+ appWidgetView.setImageViewBitmap(
+ R.id.button_prev,
+ context.getTintedDrawable(
+ R.drawable.ic_skip_previous,
+ secondaryColor
+ ).toBitmap()
+ )
+ appWidgetView.setImageViewBitmap(
+ R.id.button_toggle_play_pause,
+ context.getTintedDrawable(
+ R.drawable.ic_play_arrow_white_32dp,
+ secondaryColor
+ ).toBitmap()
+ )
+
+ linkButtons(context, appWidgetView)
+ pushUpdate(context, appWidgetIds, appWidgetView)
+ }
+
+ /**
+ * Update all active widget instances by pushing changes
+ */
+ override fun performUpdate(service: MusicService, appWidgetIds: IntArray?) {
+ val appWidgetView = RemoteViews(service.packageName, R.layout.app_widget_card)
+
+ val isPlaying = service.isPlaying
+ val song = service.currentSong
+
+ // Set the titles and artwork
+ if (song.title.isEmpty() && song.artistName.isEmpty()) {
+ appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
+ } else {
+ appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE)
+ appWidgetView.setTextViewText(R.id.title, song.title)
+ appWidgetView.setTextViewText(R.id.text, getSongArtistAndAlbum(song))
+ }
+
+ // Set correct drawable for pause state
+ val playPauseRes =
+ if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play_arrow_white_32dp
+ appWidgetView.setImageViewBitmap(
+ R.id.button_toggle_play_pause,
+ service.getTintedDrawable(
+ playPauseRes,
+ MaterialValueHelper.getSecondaryTextColor(service, true)
+ ).toBitmap()
+ )
+
+ // Set prev/next button drawables
+ appWidgetView.setImageViewBitmap(
+ R.id.button_next,
+ service.getTintedDrawable(
+ R.drawable.ic_skip_next,
+ MaterialValueHelper.getSecondaryTextColor(service, true)
+ ).toBitmap()
+ )
+ appWidgetView.setImageViewBitmap(
+ R.id.button_prev,
+ service.getTintedDrawable(
+ R.drawable.ic_skip_previous,
+ MaterialValueHelper.getSecondaryTextColor(service, true)
+ ).toBitmap()
+ )
+
+ // Link actions buttons to intents
+ linkButtons(service, appWidgetView)
+
+ if (imageSize == 0) {
+ imageSize =
+ service.resources.getDimensionPixelSize(R.dimen.app_widget_card_image_size)
+ }
+ if (cardRadius == 0f) {
+ cardRadius =
+ service.resources.getDimension(R.dimen.app_widget_card_radius)
+ }
+
+ // Load the album cover async and push the update on completion
+ service.runOnUiThread {
+ if (target != null) {
+ Glide.with(service).clear(target)
+ }
+ target = Glide.with(service)
+ .asBitmapPalette()
+ .songCoverOptions(song)
+ .load(RetroGlideExtension.getSongModel(song))
+ .centerCrop()
+ .into(object : CustomTarget(imageSize, imageSize) {
+ override fun onResourceReady(
+ resource: BitmapPaletteWrapper,
+ transition: Transition?,
+ ) {
+ val palette = resource.palette
+ update(
+ resource.bitmap, palette.getVibrantColor(
+ palette.getMutedColor(
+ MaterialValueHelper.getSecondaryTextColor(
+ service, true
+ )
+ )
+ )
+ )
+ }
+
+ override fun onLoadFailed(errorDrawable: Drawable?) {
+ super.onLoadFailed(errorDrawable)
+ update(null, MaterialValueHelper.getSecondaryTextColor(service, true))
+ }
+
+ override fun onLoadCleared(placeholder: Drawable?) {}
+
+ private fun update(bitmap: Bitmap?, color: Int) {
+ // Set correct drawable for pause state
+ appWidgetView.setImageViewBitmap(
+ R.id.button_toggle_play_pause,
+ service.getTintedDrawable(playPauseRes, color).toBitmap()
+ )
+
+ // Set prev/next button drawables
+ appWidgetView.setImageViewBitmap(
+ R.id.button_next,
+ service.getTintedDrawable(R.drawable.ic_skip_next, color).toBitmap()
+ )
+ appWidgetView.setImageViewBitmap(
+ R.id.button_prev,
+ service.getTintedDrawable(R.drawable.ic_skip_previous, color).toBitmap()
+ )
+
+ val image = getAlbumArtDrawable(service, bitmap)
+ val roundedBitmap = createRoundedBitmap(
+ image, imageSize, imageSize, cardRadius, 0F, cardRadius, 0F
+ )
+ appWidgetView.setImageViewBitmap(R.id.image, roundedBitmap)
+
+ pushUpdate(service, appWidgetIds, appWidgetView)
+ }
+ })
+ }
+ }
+
+ /**
+ * Link up various button actions using [PendingIntent].
+ */
+ private fun linkButtons(context: Context, views: RemoteViews) {
+ val action = Intent(context, MainActivity::class.java)
+ .putExtra(
+ MainActivity.EXPAND_PANEL,
+ PreferenceUtil.isExpandPanel
+ )
+
+ val serviceName = ComponentName(context, MusicService::class.java)
+
+ // Home
+ action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
+ var pendingIntent =
+ PendingIntent.getActivity(
+ context, 0, action, if (VersionUtils.hasMarshmallow())
+ PendingIntent.FLAG_IMMUTABLE
+ else 0
+ )
+ views.setOnClickPendingIntent(R.id.image, pendingIntent)
+ views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
+
+ // Previous track
+ pendingIntent = buildPendingIntent(context, ACTION_REWIND, serviceName)
+ views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
+
+ // Play and pause
+ pendingIntent = buildPendingIntent(context, ACTION_TOGGLE_PAUSE, serviceName)
+ views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
+
+ // Next track
+ pendingIntent = buildPendingIntent(context, ACTION_SKIP, serviceName)
+ views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
+ }
+
+ companion object {
+
+ const val NAME = "app_widget_card"
+
+ private var mInstance: AppWidgetCard? = null
+ private var imageSize = 0
+ private var cardRadius = 0f
+
+ val instance: AppWidgetCard
+ @Synchronized get() {
+ if (mInstance == null) {
+ mInstance = AppWidgetCard()
+ }
+ return mInstance!!
+ }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCircle.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCircle.kt
new file mode 100644
index 000000000..cb3ae0e44
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCircle.kt
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.appwidgets
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.widget.RemoteViews
+import androidx.core.graphics.drawable.toBitmap
+import code.name.monkey.appthemehelper.util.MaterialValueHelper
+import code.name.monkey.appthemehelper.util.VersionUtils
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.activities.MainActivity
+import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
+import code.name.monkey.retromusic.extensions.getTintedDrawable
+import code.name.monkey.retromusic.glide.RetroGlideExtension
+import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
+import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
+import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
+import code.name.monkey.retromusic.service.MusicService
+import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
+import code.name.monkey.retromusic.service.MusicService.Companion.TOGGLE_FAVORITE
+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.RequestOptions
+import com.bumptech.glide.request.target.CustomTarget
+import com.bumptech.glide.request.target.Target
+import com.bumptech.glide.request.transition.Transition
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+
+class AppWidgetCircle : BaseAppWidget() {
+ private var target: Target? = null // for cancellation
+
+ /**
+ * Initialize given widgets to default state, where we launch Music on default click and hide
+ * actions if service not running.
+ */
+ override fun defaultAppWidget(context: Context, appWidgetIds: IntArray) {
+ val appWidgetView = RemoteViews(context.packageName, R.layout.app_widget_circle)
+
+ appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
+ val secondaryColor = MaterialValueHelper.getSecondaryTextColor(context, true)
+ appWidgetView.setImageViewBitmap(
+ R.id.button_toggle_play_pause,
+ context.getTintedDrawable(
+ R.drawable.ic_play_arrow,
+ secondaryColor
+ ).toBitmap()
+ )
+
+ linkButtons(context, appWidgetView)
+ pushUpdate(context, appWidgetIds, appWidgetView)
+ }
+
+ /**
+ * Update all active widget instances by pushing changes
+ */
+ override fun performUpdate(service: MusicService, appWidgetIds: IntArray?) {
+ val appWidgetView = RemoteViews(service.packageName, R.layout.app_widget_circle)
+
+ val isPlaying = service.isPlaying
+ val song = service.currentSong
+
+ // Set correct drawable for pause state
+ val playPauseRes =
+ if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play_arrow
+ appWidgetView.setImageViewBitmap(
+ R.id.button_toggle_play_pause,
+ service.getTintedDrawable(
+ playPauseRes,
+ MaterialValueHelper.getSecondaryTextColor(service, true)
+ ).toBitmap()
+ )
+ val isFavorite = runBlocking(Dispatchers.IO) {
+ return@runBlocking MusicUtil.isFavorite(song)
+ }
+ val favoriteRes =
+ if (isFavorite) R.drawable.ic_favorite else R.drawable.ic_favorite_border
+ appWidgetView.setImageViewBitmap(
+ R.id.button_toggle_favorite,
+ service.getTintedDrawable(
+ favoriteRes,
+ MaterialValueHelper.getSecondaryTextColor(service, true)
+ ).toBitmap()
+ )
+
+ // Link actions buttons to intents
+ linkButtons(service, appWidgetView)
+
+ if (imageSize == 0) {
+ val p = RetroUtil.getScreenSize(service)
+ imageSize = p.x.coerceAtMost(p.y)
+ }
+
+ // Load the album cover async and push the update on completion
+ service.runOnUiThread {
+ if (target != null) {
+ Glide.with(service).clear(target)
+ }
+ target = Glide.with(service)
+ .asBitmapPalette()
+ .songCoverOptions(song)
+ .load(RetroGlideExtension.getSongModel(song))
+ .apply(RequestOptions.circleCropTransform())
+ .into(object : CustomTarget(imageSize, imageSize) {
+ override fun onResourceReady(
+ resource: BitmapPaletteWrapper,
+ transition: Transition?,
+ ) {
+ val palette = resource.palette
+ update(
+ resource.bitmap, palette.getVibrantColor(
+ palette.getMutedColor(
+ MaterialValueHelper.getSecondaryTextColor(
+ service, true
+ )
+ )
+ )
+ )
+ }
+
+ override fun onLoadFailed(errorDrawable: Drawable?) {
+ super.onLoadFailed(errorDrawable)
+ update(null, MaterialValueHelper.getSecondaryTextColor(service, true))
+ }
+
+ private fun update(bitmap: Bitmap?, color: Int) {
+ // Set correct drawable for pause state
+ appWidgetView.setImageViewBitmap(
+ R.id.button_toggle_play_pause,
+ service.getTintedDrawable(
+ playPauseRes, color
+ ).toBitmap()
+ )
+
+ // Set favorite button drawables
+ appWidgetView.setImageViewBitmap(
+ R.id.button_toggle_favorite,
+ service.getTintedDrawable(
+ favoriteRes, color
+ ).toBitmap()
+ )
+ if (bitmap != null) {
+ appWidgetView.setImageViewBitmap(R.id.image, bitmap)
+ }
+
+ pushUpdate(service, appWidgetIds, appWidgetView)
+ }
+
+ override fun onLoadCleared(placeholder: Drawable?) {}
+ })
+ }
+ }
+
+ /**
+ * Link up various button actions using [PendingIntent].
+ */
+ private fun linkButtons(context: Context, views: RemoteViews) {
+ val action = Intent(context, MainActivity::class.java)
+ .putExtra(
+ MainActivity.EXPAND_PANEL,
+ PreferenceUtil.isExpandPanel
+ )
+
+ val serviceName = ComponentName(context, MusicService::class.java)
+
+ // Home
+ action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
+ var pendingIntent =
+ PendingIntent.getActivity(
+ context, 0, action, if (VersionUtils.hasMarshmallow())
+ PendingIntent.FLAG_IMMUTABLE
+ else 0
+ )
+ views.setOnClickPendingIntent(R.id.image, pendingIntent)
+ // Favorite track
+ pendingIntent = buildPendingIntent(context, TOGGLE_FAVORITE, serviceName)
+ views.setOnClickPendingIntent(R.id.button_toggle_favorite, pendingIntent)
+
+ // Play and pause
+ pendingIntent = buildPendingIntent(context, ACTION_TOGGLE_PAUSE, serviceName)
+ views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
+ }
+
+ companion object {
+
+ const val NAME = "app_widget_circle"
+
+ private var mInstance: AppWidgetCircle? = null
+ private var imageSize = 0
+
+ val instance: AppWidgetCircle
+ @Synchronized get() {
+ if (mInstance == null) {
+ mInstance = AppWidgetCircle()
+ }
+ return mInstance!!
+ }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.java b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.java
deleted file mode 100644
index 120b9952c..000000000
--- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.java
+++ /dev/null
@@ -1,181 +0,0 @@
-package code.name.monkey.retromusic.appwidgets;
-
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-import android.support.annotation.Nullable;
-import android.support.v7.graphics.Palette;
-import android.text.TextUtils;
-import android.view.View;
-import android.widget.RemoteViews;
-import code.name.monkey.appthemehelper.util.MaterialValueHelper;
-import code.name.monkey.retromusic.Constants;
-import code.name.monkey.retromusic.R;
-import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget;
-import code.name.monkey.retromusic.glide.SongGlideRequest;
-import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper;
-import code.name.monkey.retromusic.model.Song;
-import code.name.monkey.retromusic.service.MusicService;
-import code.name.monkey.retromusic.ui.activities.MainActivity;
-import code.name.monkey.retromusic.util.RetroUtil;
-import com.bumptech.glide.Glide;
-import com.bumptech.glide.request.animation.GlideAnimation;
-import com.bumptech.glide.request.target.SimpleTarget;
-import com.bumptech.glide.request.target.Target;
-
-public class AppWidgetClassic extends BaseAppWidget {
-
- public static final String NAME = "app_widget_classic";
-
- private static AppWidgetClassic mInstance;
- private static int imageSize = 0;
- private static float cardRadius = 0f;
- private Target target; // for cancellation
-
- public static synchronized AppWidgetClassic getInstance() {
- if (mInstance == null) {
- mInstance = new AppWidgetClassic();
- }
- return mInstance;
- }
-
- /**
- * Initialize given widgets to default state, where we launch Music on default click and hide
- * actions if service not running.
- */
- protected void defaultAppWidget(final Context context, final int[] appWidgetIds) {
- final RemoteViews appWidgetView = new RemoteViews(context.getPackageName(),
- R.layout.app_widget_classic);
-
- appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE);
- appWidgetView.setImageViewResource(R.id.image, R.drawable.default_album_art);
- appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(
- RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_next_white_24dp,
- MaterialValueHelper.getSecondaryTextColor(context, true)), 1f));
- appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(
- RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_previous_white_24dp,
- MaterialValueHelper.getSecondaryTextColor(context, true)), 1f));
- appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap(
- RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_play_arrow_white_24dp,
- MaterialValueHelper.getSecondaryTextColor(context, true)), 1f));
-
- linkButtons(context, appWidgetView);
- pushUpdate(context, appWidgetIds, appWidgetView);
- }
-
- /**
- * Update all active widget instances by pushing changes
- */
- public void performUpdate(final MusicService service, final int[] appWidgetIds) {
- final RemoteViews appWidgetView = new RemoteViews(service.getPackageName(),
- R.layout.app_widget_classic);
-
- final boolean isPlaying = service.isPlaying();
- final Song song = service.getCurrentSong();
-
- // Set the titles and artwork
- if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
- appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE);
- } else {
- appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE);
- appWidgetView.setTextViewText(R.id.title, song.title);
- appWidgetView.setTextViewText(R.id.text, getSongArtistAndAlbum(song));
- }
-
- // Link actions buttons to intents
- linkButtons(service, appWidgetView);
-
- if (imageSize == 0) {
- imageSize = service.getResources()
- .getDimensionPixelSize(R.dimen.app_widget_classic_image_size);
- }
- if (cardRadius == 0f) {
- cardRadius = service.getResources().getDimension(R.dimen.app_widget_card_radius);
- }
-
- // Load the album cover async and push the update on completion
- final Context appContext = service.getApplicationContext();
- service.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- if (target != null) {
- Glide.clear(target);
- }
- target = SongGlideRequest.Builder.from(Glide.with(appContext), song)
- .checkIgnoreMediaStore(appContext)
- .generatePalette(service).build()
- .centerCrop()
- .into(new SimpleTarget(imageSize, imageSize) {
- @Override
- public void onResourceReady(BitmapPaletteWrapper resource,
- GlideAnimation super BitmapPaletteWrapper> glideAnimation) {
- Palette palette = resource.getPalette();
- update(resource.getBitmap(), palette.getVibrantColor(palette
- .getMutedColor(MaterialValueHelper.getSecondaryTextColor(appContext, true))));
- }
-
- @Override
- public void onLoadFailed(Exception e, Drawable errorDrawable) {
- super.onLoadFailed(e, errorDrawable);
- update(null, MaterialValueHelper.getSecondaryTextColor(appContext, true));
- }
-
- private void update(@Nullable Bitmap bitmap, int color) {
- // Set correct drawable for pause state
- int playPauseRes = isPlaying ? R.drawable.ic_pause_white_24dp
- : R.drawable.ic_play_arrow_white_24dp;
- appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause,
- createBitmap(RetroUtil.getTintedVectorDrawable(service, playPauseRes, color), 1f));
-
- // Set prev/next button drawables
- appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(
- RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp,
- color), 1f));
- appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(
- RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp,
- color), 1f));
-
- final Drawable image = getAlbumArtDrawable(service.getResources(), bitmap);
- final Bitmap roundedBitmap = createRoundedBitmap(image, imageSize, imageSize,
- cardRadius, 0, cardRadius, 0);
- appWidgetView.setImageViewBitmap(R.id.image, roundedBitmap);
-
- pushUpdate(appContext, appWidgetIds, appWidgetView);
- }
- });
- }
- });
- }
-
- /**
- * Link up various button actions using {@link PendingIntent}.
- */
- private void linkButtons(final Context context, final RemoteViews views) {
- Intent action;
- PendingIntent pendingIntent;
-
- final ComponentName serviceName = new ComponentName(context, MusicService.class);
-
- // Home
- action = new Intent(context, MainActivity.class);
- action.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- pendingIntent = PendingIntent.getActivity(context, 0, action, 0);
- views.setOnClickPendingIntent(R.id.image, pendingIntent);
- views.setOnClickPendingIntent(R.id.media_titles, pendingIntent);
-
- // Previous track
- pendingIntent = buildPendingIntent(context, Constants.ACTION_REWIND, serviceName);
- views.setOnClickPendingIntent(R.id.button_prev, pendingIntent);
-
- // Play and pause
- pendingIntent = buildPendingIntent(context, Constants.ACTION_TOGGLE_PAUSE, serviceName);
- views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent);
-
- // Next track
- pendingIntent = buildPendingIntent(context, Constants.ACTION_SKIP, serviceName);
- views.setOnClickPendingIntent(R.id.button_next, pendingIntent);
- }
-}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.kt
new file mode 100644
index 000000000..ad04f8aae
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.kt
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.appwidgets
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.view.View
+import android.widget.RemoteViews
+import androidx.core.graphics.drawable.toBitmap
+import code.name.monkey.appthemehelper.util.MaterialValueHelper
+import code.name.monkey.appthemehelper.util.VersionUtils
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.activities.MainActivity
+import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
+import code.name.monkey.retromusic.extensions.getTintedDrawable
+import code.name.monkey.retromusic.glide.RetroGlideExtension
+import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
+import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
+import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
+import code.name.monkey.retromusic.service.MusicService
+import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_REWIND
+import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP
+import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
+import code.name.monkey.retromusic.util.PreferenceUtil
+import com.bumptech.glide.Glide
+import com.bumptech.glide.request.target.CustomTarget
+import com.bumptech.glide.request.target.Target
+import com.bumptech.glide.request.transition.Transition
+
+class AppWidgetClassic : BaseAppWidget() {
+ private var target: Target? = null // for cancellation
+
+ /**
+ * Initialize given widgets to default state, where we launch Music on default click and hide
+ * actions if service not running.
+ */
+ override fun defaultAppWidget(context: Context, appWidgetIds: IntArray) {
+ val appWidgetView = RemoteViews(context.packageName, R.layout.app_widget_classic)
+
+ appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
+ appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
+ appWidgetView.setImageViewBitmap(
+ R.id.button_next,
+
+ context.getTintedDrawable(
+ R.drawable.ic_skip_next,
+ MaterialValueHelper.getSecondaryTextColor(context, true)
+ ).toBitmap()
+ )
+ appWidgetView.setImageViewBitmap(
+ R.id.button_prev,
+
+ context.getTintedDrawable(
+ R.drawable.ic_skip_previous,
+ MaterialValueHelper.getSecondaryTextColor(context, true)
+ ).toBitmap()
+ )
+ appWidgetView.setImageViewBitmap(
+ R.id.button_toggle_play_pause,
+
+ context.getTintedDrawable(
+ R.drawable.ic_play_arrow_white_32dp,
+ MaterialValueHelper.getSecondaryTextColor(context, true)
+ ).toBitmap()
+ )
+
+ linkButtons(context, appWidgetView)
+ pushUpdate(context, appWidgetIds, appWidgetView)
+ }
+
+ /**
+ * Update all active widget instances by pushing changes
+ */
+ override fun performUpdate(service: MusicService, appWidgetIds: IntArray?) {
+ val appWidgetView = RemoteViews(service.packageName, R.layout.app_widget_classic)
+
+ val isPlaying = service.isPlaying
+ val song = service.currentSong
+
+ // Set the titles and artwork
+ if (song.title.isEmpty() && song.artistName.isEmpty()) {
+ appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
+ } else {
+ appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE)
+ appWidgetView.setTextViewText(R.id.title, song.title)
+ appWidgetView.setTextViewText(R.id.text, getSongArtistAndAlbum(song))
+ }
+
+ // Link actions buttons to intents
+ linkButtons(service, appWidgetView)
+
+ if (imageSize == 0) {
+ imageSize =
+ service.resources.getDimensionPixelSize(R.dimen.app_widget_classic_image_size)
+ }
+ if (cardRadius == 0f) {
+ cardRadius = service.resources.getDimension(R.dimen.app_widget_card_radius)
+ }
+
+ // Load the album cover async and push the update on completion
+ val appContext = service.applicationContext
+ service.runOnUiThread {
+ if (target != null) {
+ Glide.with(service).clear(target)
+ }
+ target = Glide.with(service)
+ .asBitmapPalette()
+ .songCoverOptions(song)
+ .load(RetroGlideExtension.getSongModel(song))
+ //.checkIgnoreMediaStore()
+ .centerCrop()
+ .into(object : CustomTarget(imageSize, imageSize) {
+ override fun onResourceReady(
+ resource: BitmapPaletteWrapper,
+ transition: Transition?,
+ ) {
+ val palette = resource.palette
+ update(
+ resource.bitmap,
+ palette.getVibrantColor(
+ palette.getMutedColor(
+ MaterialValueHelper.getSecondaryTextColor(
+ service,
+ true
+ )
+ )
+ )
+ )
+ }
+
+ override fun onLoadFailed(errorDrawable: Drawable?) {
+ super.onLoadFailed(errorDrawable)
+ update(null, Color.WHITE)
+ }
+
+ override fun onLoadCleared(placeholder: Drawable?) {}
+
+ private fun update(bitmap: Bitmap?, color: Int) {
+ // Set correct drawable for pause state
+ val playPauseRes =
+ if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play_arrow
+ appWidgetView.setImageViewBitmap(
+ R.id.button_toggle_play_pause,
+ service.getTintedDrawable(
+ playPauseRes,
+ color
+ ).toBitmap()
+ )
+
+ // Set prev/next button drawables
+ appWidgetView.setImageViewBitmap(
+ R.id.button_next,
+ service.getTintedDrawable(
+ R.drawable.ic_skip_next,
+ color
+ ).toBitmap()
+ )
+ appWidgetView.setImageViewBitmap(
+ R.id.button_prev,
+ service.getTintedDrawable(
+ R.drawable.ic_skip_previous,
+ color
+ ).toBitmap()
+ )
+
+ val image = getAlbumArtDrawable(service, bitmap)
+ val roundedBitmap =
+ createRoundedBitmap(
+ image,
+ imageSize,
+ imageSize,
+ cardRadius,
+ 0F,
+ cardRadius,
+ 0F
+ )
+ appWidgetView.setImageViewBitmap(R.id.image, roundedBitmap)
+
+ pushUpdate(appContext, appWidgetIds, appWidgetView)
+ }
+ })
+ }
+ }
+
+ /**
+ * Link up various button actions using [PendingIntent].
+ */
+ private fun linkButtons(context: Context, views: RemoteViews) {
+ val action = Intent(context, MainActivity::class.java)
+ .putExtra(
+ MainActivity.EXPAND_PANEL,
+ PreferenceUtil.isExpandPanel
+ )
+
+ val serviceName = ComponentName(context, MusicService::class.java)
+
+ // Home
+ action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
+ var pendingIntent = PendingIntent.getActivity(
+ context, 0, action, if (VersionUtils.hasMarshmallow())
+ PendingIntent.FLAG_IMMUTABLE
+ else 0
+ )
+ views.setOnClickPendingIntent(R.id.image, pendingIntent)
+ views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
+
+ // Previous track
+ pendingIntent = buildPendingIntent(context, ACTION_REWIND, serviceName)
+ views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
+
+ // Play and pause
+ pendingIntent = buildPendingIntent(context, ACTION_TOGGLE_PAUSE, serviceName)
+ views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
+
+ // Next track
+ pendingIntent = buildPendingIntent(context, ACTION_SKIP, serviceName)
+ views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
+ }
+
+ companion object {
+
+ const val NAME = "app_widget_classic"
+
+ private var mInstance: AppWidgetClassic? = null
+ private var imageSize = 0
+ private var cardRadius = 0f
+
+ val instance: AppWidgetClassic
+ @Synchronized get() {
+ if (mInstance == null) {
+ mInstance = AppWidgetClassic()
+ }
+ return mInstance!!
+ }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetMD3.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetMD3.kt
new file mode 100644
index 000000000..618914ff3
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetMD3.kt
@@ -0,0 +1,264 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.appwidgets
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.view.View
+import android.widget.RemoteViews
+import androidx.core.graphics.drawable.toBitmap
+import code.name.monkey.appthemehelper.util.MaterialValueHelper
+import code.name.monkey.appthemehelper.util.VersionUtils
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.activities.MainActivity
+import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
+import code.name.monkey.retromusic.extensions.getTintedDrawable
+import code.name.monkey.retromusic.glide.RetroGlideExtension
+import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
+import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
+import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
+import code.name.monkey.retromusic.service.MusicService
+import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_REWIND
+import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP
+import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
+import code.name.monkey.retromusic.util.DensityUtil
+import code.name.monkey.retromusic.util.PreferenceUtil
+import com.bumptech.glide.Glide
+import com.bumptech.glide.request.target.CustomTarget
+import com.bumptech.glide.request.target.Target
+import com.bumptech.glide.request.transition.Transition
+
+class AppWidgetMD3 : BaseAppWidget() {
+ private var target: Target? = null // for cancellation
+
+ /**
+ * Initialize given widgets to default state, where we launch Music on default click and hide
+ * actions if service not running.
+ */
+ override fun defaultAppWidget(context: Context, appWidgetIds: IntArray) {
+ val appWidgetView = RemoteViews(context.packageName, R.layout.app_widget_md3)
+
+ appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
+ appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
+ val secondaryColor = MaterialValueHelper.getSecondaryTextColor(context, true)
+ appWidgetView.setImageViewBitmap(
+ R.id.button_next,
+ context.getTintedDrawable(
+ R.drawable.ic_skip_next,
+ secondaryColor
+ ).toBitmap()
+ )
+ appWidgetView.setImageViewBitmap(
+ R.id.button_prev,
+ context.getTintedDrawable(
+ R.drawable.ic_skip_previous,
+ secondaryColor
+ ).toBitmap()
+ )
+ appWidgetView.setImageViewBitmap(
+ R.id.button_toggle_play_pause,
+ context.getTintedDrawable(
+ R.drawable.ic_play_arrow_white_32dp,
+ secondaryColor
+ ).toBitmap()
+ )
+
+ linkButtons(context, appWidgetView)
+ pushUpdate(context, appWidgetIds, appWidgetView)
+ }
+
+ /**
+ * Update all active widget instances by pushing changes
+ */
+ override fun performUpdate(service: MusicService, appWidgetIds: IntArray?) {
+ val appWidgetView = RemoteViews(service.packageName, R.layout.app_widget_md3)
+
+ val isPlaying = service.isPlaying
+ val song = service.currentSong
+
+ // Set the titles and artwork
+ if (song.title.isEmpty() && song.artistName.isEmpty()) {
+ appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
+ } else {
+ appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE)
+ appWidgetView.setTextViewText(R.id.title, song.title)
+ appWidgetView.setTextViewText(R.id.text, getSongArtistAndAlbum(song))
+ }
+
+ // Set correct drawable for pause state
+ val playPauseRes =
+ if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play_arrow_white_32dp
+ appWidgetView.setImageViewBitmap(
+ R.id.button_toggle_play_pause,
+ service.getTintedDrawable(
+ playPauseRes,
+ MaterialValueHelper.getSecondaryTextColor(service, true)
+ ).toBitmap()
+ )
+
+ // Set prev/next button drawables
+ appWidgetView.setImageViewBitmap(
+ R.id.button_next,
+ service.getTintedDrawable(
+ R.drawable.ic_skip_next,
+ MaterialValueHelper.getSecondaryTextColor(service, true)
+ ).toBitmap()
+ )
+ appWidgetView.setImageViewBitmap(
+ R.id.button_prev,
+ service.getTintedDrawable(
+ R.drawable.ic_skip_previous,
+ MaterialValueHelper.getSecondaryTextColor(service, true)
+ ).toBitmap()
+ )
+
+ // Link actions buttons to intents
+ linkButtons(service, appWidgetView)
+
+ if (imageSize == 0) {
+ imageSize =
+ service.resources.getDimensionPixelSize(R.dimen.app_widget_card_image_size)
+ }
+ if (cardRadius == 0f) {
+ cardRadius =
+ DensityUtil.dip2px(service, 8F).toFloat()
+ }
+
+ // Load the album cover async and push the update on completion
+ service.runOnUiThread {
+ if (target != null) {
+ Glide.with(service).clear(target)
+ }
+ target = Glide.with(service)
+ .asBitmapPalette()
+ .songCoverOptions(song)
+ .load(RetroGlideExtension.getSongModel(song))
+ .centerCrop()
+ .into(object : CustomTarget(imageSize, imageSize) {
+ override fun onResourceReady(
+ resource: BitmapPaletteWrapper,
+ transition: Transition?,
+ ) {
+ val palette = resource.palette
+ update(
+ resource.bitmap, palette.getVibrantColor(
+ palette.getMutedColor(
+ MaterialValueHelper.getSecondaryTextColor(
+ service, true
+ )
+ )
+ )
+ )
+ }
+
+ override fun onLoadFailed(errorDrawable: Drawable?) {
+ super.onLoadFailed(errorDrawable)
+ update(null, MaterialValueHelper.getSecondaryTextColor(service, true))
+ }
+
+ override fun onLoadCleared(placeholder: Drawable?) {}
+
+ private fun update(bitmap: Bitmap?, color: Int) {
+ // Set correct drawable for pause state
+ appWidgetView.setImageViewBitmap(
+ R.id.button_toggle_play_pause,
+ service.getTintedDrawable(playPauseRes, color).toBitmap()
+ )
+
+ // Set prev/next button drawables
+ appWidgetView.setImageViewBitmap(
+ R.id.button_next,
+ service.getTintedDrawable(R.drawable.ic_skip_next, color).toBitmap()
+ )
+ appWidgetView.setImageViewBitmap(
+ R.id.button_prev,
+ service.getTintedDrawable(R.drawable.ic_skip_previous, color).toBitmap()
+ )
+
+ val image = getAlbumArtDrawable(service, bitmap)
+ val roundedBitmap = createRoundedBitmap(
+ image,
+ imageSize,
+ imageSize,
+ cardRadius,
+ cardRadius,
+ cardRadius,
+ cardRadius
+ )
+ appWidgetView.setImageViewBitmap(R.id.image, roundedBitmap)
+
+ pushUpdate(service, appWidgetIds, appWidgetView)
+ }
+ })
+ }
+ }
+
+ /**
+ * Link up various button actions using [PendingIntent].
+ */
+ private fun linkButtons(context: Context, views: RemoteViews) {
+ val action = Intent(context, MainActivity::class.java)
+ .putExtra(
+ MainActivity.EXPAND_PANEL,
+ PreferenceUtil.isExpandPanel
+ )
+
+ val serviceName = ComponentName(context, MusicService::class.java)
+
+ // Home
+ action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
+ var pendingIntent =
+ PendingIntent.getActivity(
+ context, 0, action, if (VersionUtils.hasMarshmallow())
+ PendingIntent.FLAG_IMMUTABLE
+ else 0
+ )
+ views.setOnClickPendingIntent(R.id.image, pendingIntent)
+ views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
+
+ // Previous track
+ pendingIntent = buildPendingIntent(context, ACTION_REWIND, serviceName)
+ views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
+
+ // Play and pause
+ pendingIntent = buildPendingIntent(context, ACTION_TOGGLE_PAUSE, serviceName)
+ views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
+
+ // Next track
+ pendingIntent = buildPendingIntent(context, ACTION_SKIP, serviceName)
+ views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
+ }
+
+ companion object {
+
+ const val NAME = "app_widget_md3"
+
+ private var mInstance: AppWidgetMD3? = null
+ private var imageSize = 0
+ private var cardRadius = 0F
+
+ val instance: AppWidgetMD3
+ @Synchronized get() {
+ if (mInstance == null) {
+ mInstance = AppWidgetMD3()
+ }
+ return mInstance!!
+ }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.java b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.java
deleted file mode 100644
index 1d80a2d13..000000000
--- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.java
+++ /dev/null
@@ -1,186 +0,0 @@
-package code.name.monkey.retromusic.appwidgets;
-
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-import android.support.annotation.Nullable;
-import android.support.v7.graphics.Palette;
-import android.text.TextUtils;
-import android.view.View;
-import android.widget.RemoteViews;
-import code.name.monkey.appthemehelper.util.MaterialValueHelper;
-import code.name.monkey.retromusic.Constants;
-import code.name.monkey.retromusic.R;
-import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget;
-import code.name.monkey.retromusic.glide.SongGlideRequest;
-import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper;
-import code.name.monkey.retromusic.model.Song;
-import code.name.monkey.retromusic.service.MusicService;
-import code.name.monkey.retromusic.ui.activities.MainActivity;
-import code.name.monkey.retromusic.util.RetroUtil;
-import com.bumptech.glide.Glide;
-import com.bumptech.glide.request.animation.GlideAnimation;
-import com.bumptech.glide.request.target.SimpleTarget;
-import com.bumptech.glide.request.target.Target;
-
-public class AppWidgetSmall extends BaseAppWidget {
-
- public static final String NAME = "app_widget_small";
-
- private static AppWidgetSmall mInstance;
- private static int imageSize = 0;
- private static float cardRadius = 0f;
- private Target target; // for cancellation
-
- public static synchronized AppWidgetSmall getInstance() {
- if (mInstance == null) {
- mInstance = new AppWidgetSmall();
- }
- return mInstance;
- }
-
- /**
- * Initialize given widgets to default state, where we launch Music on default click and hide
- * actions if service not running.
- */
- protected void defaultAppWidget(final Context context, final int[] appWidgetIds) {
- final RemoteViews appWidgetView = new RemoteViews(context.getPackageName(),
- R.layout.app_widget_small);
-
- appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE);
- appWidgetView.setImageViewResource(R.id.image, R.drawable.default_album_art);
- appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(
- RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_next_white_24dp,
- MaterialValueHelper.getSecondaryTextColor(context, true)), 1f));
- appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(
- RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_previous_white_24dp,
- MaterialValueHelper.getSecondaryTextColor(context, true)), 1f));
- appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap(
- RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_play_arrow_white_24dp,
- MaterialValueHelper.getSecondaryTextColor(context, true)), 1f));
-
- linkButtons(context, appWidgetView);
- pushUpdate(context, appWidgetIds, appWidgetView);
- }
-
- /**
- * Update all active widget instances by pushing changes
- */
- public void performUpdate(final MusicService service, final int[] appWidgetIds) {
- final RemoteViews appWidgetView = new RemoteViews(service.getPackageName(),
- R.layout.app_widget_small);
-
- final boolean isPlaying = service.isPlaying();
- final Song song = service.getCurrentSong();
-
- // Set the titles and artwork
- if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
- appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE);
- } else {
- if (TextUtils.isEmpty(song.title) || TextUtils.isEmpty(song.artistName)) {
- appWidgetView.setTextViewText(R.id.text_separator, "");
- } else {
- appWidgetView.setTextViewText(R.id.text_separator, "•");
- }
-
- appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE);
- appWidgetView.setTextViewText(R.id.title, song.title);
- appWidgetView.setTextViewText(R.id.text, song.artistName);
- }
-
- // Link actions buttons to intents
- linkButtons(service, appWidgetView);
-
- if (imageSize == 0) {
- imageSize = service.getResources().getDimensionPixelSize(R.dimen.app_widget_small_image_size);
- }
- if (cardRadius == 0f) {
- cardRadius = service.getResources().getDimension(R.dimen.app_widget_card_radius);
- }
-
- // Load the album cover async and push the update on completion
- final Context appContext = service.getApplicationContext();
- service.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- if (target != null) {
- Glide.clear(target);
- }
- target = SongGlideRequest.Builder.from(Glide.with(appContext), song)
- .checkIgnoreMediaStore(appContext)
- .generatePalette(service).build()
- .centerCrop()
- .into(new SimpleTarget(imageSize, imageSize) {
- @Override
- public void onResourceReady(BitmapPaletteWrapper resource,
- GlideAnimation super BitmapPaletteWrapper> glideAnimation) {
- Palette palette = resource.getPalette();
- update(resource.getBitmap(), palette.getVibrantColor(palette
- .getMutedColor(MaterialValueHelper.getSecondaryTextColor(appContext, true))));
- }
-
- @Override
- public void onLoadFailed(Exception e, Drawable errorDrawable) {
- super.onLoadFailed(e, errorDrawable);
- update(null, MaterialValueHelper.getSecondaryTextColor(appContext, true));
- }
-
- private void update(@Nullable Bitmap bitmap, int color) {
- // Set correct drawable for pause state
- int playPauseRes = isPlaying ? R.drawable.ic_pause_white_24dp
- : R.drawable.ic_play_arrow_white_24dp;
- appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause,
- createBitmap(RetroUtil.getTintedVectorDrawable(service, playPauseRes, color), 1f));
-
- // Set prev/next button drawables
- appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(
- RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp,
- color), 1f));
- appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(
- RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp,
- color), 1f));
-
- final Drawable image = getAlbumArtDrawable(service.getResources(), bitmap);
- final Bitmap roundedBitmap = createRoundedBitmap(image, imageSize, imageSize,
- cardRadius, 0, 0, 0);
- appWidgetView.setImageViewBitmap(R.id.image, roundedBitmap);
-
- pushUpdate(appContext, appWidgetIds, appWidgetView);
- }
- });
- }
- });
- }
-
- /**
- * Link up various button actions using {@link PendingIntent}.
- */
- private void linkButtons(final Context context, final RemoteViews views) {
- Intent action;
- PendingIntent pendingIntent;
-
- final ComponentName serviceName = new ComponentName(context, MusicService.class);
-
- // Home
- action = new Intent(context, MainActivity.class);
- action.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- pendingIntent = PendingIntent.getActivity(context, 0, action, 0);
- views.setOnClickPendingIntent(R.id.image, pendingIntent);
- views.setOnClickPendingIntent(R.id.media_titles, pendingIntent);
-
- // Previous track
- pendingIntent = buildPendingIntent(context, Constants.ACTION_REWIND, serviceName);
- views.setOnClickPendingIntent(R.id.button_prev, pendingIntent);
-
- // Play and pause
- pendingIntent = buildPendingIntent(context, Constants.ACTION_TOGGLE_PAUSE, serviceName);
- views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent);
-
- // Next track
- pendingIntent = buildPendingIntent(context, Constants.ACTION_SKIP, serviceName);
- views.setOnClickPendingIntent(R.id.button_next, pendingIntent);
- }
-}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.kt
new file mode 100644
index 000000000..ee4e87e99
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.appwidgets
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.view.View
+import android.widget.RemoteViews
+import androidx.core.graphics.drawable.toBitmap
+import code.name.monkey.appthemehelper.util.MaterialValueHelper
+import code.name.monkey.appthemehelper.util.VersionUtils
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.activities.MainActivity
+import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
+import code.name.monkey.retromusic.extensions.getTintedDrawable
+import code.name.monkey.retromusic.glide.RetroGlideExtension
+import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
+import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
+import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
+import code.name.monkey.retromusic.service.MusicService
+import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_REWIND
+import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP
+import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
+import code.name.monkey.retromusic.util.PreferenceUtil
+import com.bumptech.glide.Glide
+import com.bumptech.glide.request.target.CustomTarget
+import com.bumptech.glide.request.target.Target
+import com.bumptech.glide.request.transition.Transition
+
+class AppWidgetSmall : BaseAppWidget() {
+ private var target: Target? = null // for cancellation
+
+ /**
+ * Initialize given widgets to default state, where we launch Music on default click and hide
+ * actions if service not running.
+ */
+ override fun defaultAppWidget(context: Context, appWidgetIds: IntArray) {
+ val appWidgetView = RemoteViews(context.packageName, R.layout.app_widget_small)
+
+ appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
+ appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
+ appWidgetView.setImageViewBitmap(
+ R.id.button_next,
+ context.getTintedDrawable(
+ R.drawable.ic_skip_next,
+ MaterialValueHelper.getSecondaryTextColor(context, true)
+ ).toBitmap()
+ )
+ appWidgetView.setImageViewBitmap(
+ R.id.button_prev,
+
+ context.getTintedDrawable(
+ R.drawable.ic_skip_previous,
+ MaterialValueHelper.getSecondaryTextColor(context, true)
+ ).toBitmap()
+ )
+ appWidgetView.setImageViewBitmap(
+ R.id.button_toggle_play_pause,
+
+ context.getTintedDrawable(
+ R.drawable.ic_play_arrow_white_32dp,
+ MaterialValueHelper.getSecondaryTextColor(context, true)
+ ).toBitmap()
+ )
+
+ linkButtons(context, appWidgetView)
+ pushUpdate(context, appWidgetIds, appWidgetView)
+ }
+
+ /**
+ * Update all active widget instances by pushing changes
+ */
+ override fun performUpdate(service: MusicService, appWidgetIds: IntArray?) {
+ val appWidgetView = RemoteViews(service.packageName, R.layout.app_widget_small)
+
+ val isPlaying = service.isPlaying
+ val song = service.currentSong
+
+ // Set the titles and artwork
+ if (song.title.isEmpty() && song.artistName.isEmpty()) {
+ appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
+ } else {
+ if (song.title.isEmpty() || song.artistName.isEmpty()) {
+ appWidgetView.setTextViewText(R.id.text_separator, "")
+ } else {
+ appWidgetView.setTextViewText(R.id.text_separator, "•")
+ }
+
+ appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE)
+ appWidgetView.setTextViewText(R.id.title, song.title)
+ appWidgetView.setTextViewText(R.id.text, song.artistName)
+ }
+
+ // Link actions buttons to intents
+ linkButtons(service, appWidgetView)
+
+ if (imageSize == 0) {
+ imageSize = service.resources.getDimensionPixelSize(R.dimen.app_widget_small_image_size)
+ }
+ if (cardRadius == 0f) {
+ cardRadius = service.resources.getDimension(R.dimen.app_widget_card_radius)
+ }
+
+ // Load the album cover async and push the update on completion
+ val appContext = service.applicationContext
+ service.runOnUiThread {
+ if (target != null) {
+ Glide.with(service).clear(target)
+ }
+ target = Glide.with(service)
+ .asBitmapPalette()
+ .songCoverOptions(song)
+ //.checkIgnoreMediaStore()
+ .load(RetroGlideExtension.getSongModel(song))
+ .centerCrop()
+ .into(object : CustomTarget(imageSize, imageSize) {
+ override fun onResourceReady(
+ resource: BitmapPaletteWrapper,
+ transition: Transition?,
+ ) {
+ val palette = resource.palette
+ update(
+ resource.bitmap, palette.getVibrantColor(
+ palette.getMutedColor(
+ MaterialValueHelper.getSecondaryTextColor(
+ service, true
+ )
+ )
+ )
+ )
+ }
+
+ override fun onLoadFailed(errorDrawable: Drawable?) {
+ super.onLoadFailed(errorDrawable)
+ update(null, MaterialValueHelper.getSecondaryTextColor(service, true))
+ }
+
+ override fun onLoadCleared(placeholder: Drawable?) {
+ update(null, MaterialValueHelper.getSecondaryTextColor(service, true))
+ }
+
+ private fun update(bitmap: Bitmap?, color: Int) {
+ // Set correct drawable for pause state
+ val playPauseRes = if (isPlaying) R.drawable.ic_pause
+ else R.drawable.ic_play_arrow_white_32dp
+ appWidgetView.setImageViewBitmap(
+ R.id.button_toggle_play_pause,
+ service.getTintedDrawable(playPauseRes, color).toBitmap()
+ )
+
+ // Set prev/next button drawables
+ appWidgetView.setImageViewBitmap(
+ R.id.button_next,
+ service.getTintedDrawable(R.drawable.ic_skip_next, color).toBitmap()
+ )
+ appWidgetView.setImageViewBitmap(
+ R.id.button_prev,
+ service.getTintedDrawable(R.drawable.ic_skip_previous, color).toBitmap()
+ )
+
+ val image = getAlbumArtDrawable(service, bitmap)
+ val roundedBitmap = createRoundedBitmap(
+ image, imageSize, imageSize, cardRadius, 0f, 0f, 0f
+ )
+ appWidgetView.setImageViewBitmap(R.id.image, roundedBitmap)
+
+ pushUpdate(appContext, appWidgetIds, appWidgetView)
+ }
+ })
+ }
+ }
+
+ /**
+ * Link up various button actions using [PendingIntent].
+ */
+ private fun linkButtons(context: Context, views: RemoteViews) {
+ val action = Intent(context, MainActivity::class.java)
+ .putExtra(
+ MainActivity.EXPAND_PANEL,
+ PreferenceUtil.isExpandPanel
+ )
+
+ val serviceName = ComponentName(context, MusicService::class.java)
+
+ // Home
+ action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
+ var pendingIntent =
+ PendingIntent.getActivity(
+ context, 0, action, if (VersionUtils.hasMarshmallow())
+ PendingIntent.FLAG_IMMUTABLE
+ else 0
+ )
+ views.setOnClickPendingIntent(R.id.image, pendingIntent)
+ views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
+
+ // Previous track
+ pendingIntent = buildPendingIntent(context, ACTION_REWIND, serviceName)
+ views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
+
+ // Play and pause
+ pendingIntent = buildPendingIntent(context, ACTION_TOGGLE_PAUSE, serviceName)
+ views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
+
+ // Next track
+ pendingIntent = buildPendingIntent(context, ACTION_SKIP, serviceName)
+ views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
+ }
+
+ companion object {
+
+ const val NAME: String = "app_widget_small"
+
+ private var mInstance: AppWidgetSmall? = null
+ private var imageSize = 0
+ private var cardRadius = 0f
+
+ val instance: AppWidgetSmall
+ @Synchronized get() {
+ if (mInstance == null) {
+ mInstance = AppWidgetSmall()
+ }
+ return mInstance!!
+ }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetText.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetText.kt
new file mode 100644
index 000000000..b7abb6ee5
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetText.kt
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.appwidgets
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.view.View
+import android.widget.RemoteViews
+import androidx.core.content.ContextCompat
+import androidx.core.graphics.drawable.toBitmap
+import code.name.monkey.appthemehelper.util.VersionUtils
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.activities.MainActivity
+import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
+import code.name.monkey.retromusic.extensions.getTintedDrawable
+import code.name.monkey.retromusic.service.MusicService
+import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_REWIND
+import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP
+import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
+import code.name.monkey.retromusic.util.PreferenceUtil
+
+class AppWidgetText : BaseAppWidget() {
+ override fun defaultAppWidget(context: Context, appWidgetIds: IntArray) {
+ val appWidgetView = RemoteViews(context.packageName, R.layout.app_widget_text)
+
+ appWidgetView.setImageViewBitmap(
+ R.id.button_next,
+ context.getTintedDrawable(R.drawable.ic_skip_next, ContextCompat.getColor(
+ context, code.name.monkey.appthemehelper.R.color.md_white_1000
+ )).toBitmap()
+ )
+ appWidgetView.setImageViewBitmap(
+ R.id.button_prev,
+ context.getTintedDrawable(R.drawable.ic_skip_previous, ContextCompat.getColor(
+ context, code.name.monkey.appthemehelper.R.color.md_white_1000
+ )
+ ).toBitmap()
+ )
+ appWidgetView.setImageViewBitmap(
+ R.id.button_toggle_play_pause,
+ context.getTintedDrawable(R.drawable.ic_play_arrow_white_32dp, ContextCompat.getColor(
+ context, code.name.monkey.appthemehelper.R.color.md_white_1000
+ )
+ ).toBitmap()
+ )
+
+ appWidgetView.setTextColor(
+ R.id.title, ContextCompat.getColor(context, code.name.monkey.appthemehelper.R.color.md_white_1000)
+ )
+ appWidgetView.setTextColor(
+ R.id.text, ContextCompat.getColor(context, code.name.monkey.appthemehelper.R.color.md_white_1000)
+ )
+
+ linkButtons(context, appWidgetView)
+ pushUpdate(context, appWidgetIds, appWidgetView)
+ }
+
+ /**
+ * Link up various button actions using [PendingIntent].
+ */
+ private fun linkButtons(context: Context, views: RemoteViews) {
+ val action = Intent(context, MainActivity::class.java)
+ .putExtra(
+ MainActivity.EXPAND_PANEL,
+ PreferenceUtil.isExpandPanel
+ )
+
+ val serviceName = ComponentName(context, MusicService::class.java)
+
+ // Home
+ action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
+ var pendingIntent = PendingIntent.getActivity(
+ context, 0, action, if (VersionUtils.hasMarshmallow())
+ PendingIntent.FLAG_IMMUTABLE
+ else 0
+ )
+ views.setOnClickPendingIntent(R.id.image, pendingIntent)
+ views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
+
+ // Previous track
+ pendingIntent = buildPendingIntent(context, ACTION_REWIND, serviceName)
+ views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
+
+ // Play and pause
+ pendingIntent = buildPendingIntent(context, ACTION_TOGGLE_PAUSE, serviceName)
+ views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
+
+ // Next track
+ pendingIntent = buildPendingIntent(context, ACTION_SKIP, serviceName)
+ views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
+ }
+
+ override fun performUpdate(service: MusicService, appWidgetIds: IntArray?) {
+ val appWidgetView = RemoteViews(service.packageName, R.layout.app_widget_text)
+
+ val isPlaying = service.isPlaying
+ val song = service.currentSong
+
+ // Set the titles and artwork
+ if (song.title.isEmpty() && song.artistName.isEmpty()) {
+ appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
+ } else {
+ appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE)
+ appWidgetView.setTextViewText(R.id.title, song.title)
+ appWidgetView.setTextViewText(R.id.text, song.artistName)
+ }
+ // Link actions buttons to intents
+ linkButtons(service, appWidgetView)
+
+ // Set correct drawable for pause state
+ val playPauseRes = if (isPlaying) R.drawable.ic_pause
+ else R.drawable.ic_play_arrow_white_32dp
+ appWidgetView.setImageViewBitmap(
+ R.id.button_toggle_play_pause,
+ service.getTintedDrawable(playPauseRes, ContextCompat.getColor(
+ service, code.name.monkey.appthemehelper.R.color.md_white_1000)
+ ).toBitmap()
+ )
+ appWidgetView.setImageViewBitmap(
+ R.id.button_next,
+ service.getTintedDrawable(
+ R.drawable.ic_skip_next,
+ ContextCompat.getColor(
+ service,
+ code.name.monkey.appthemehelper.R.color.md_white_1000
+ )
+ ).toBitmap()
+ )
+ appWidgetView.setImageViewBitmap(
+ R.id.button_prev,
+ service.getTintedDrawable(
+ R.drawable.ic_skip_previous,
+ ContextCompat.getColor(
+ service, code.name.monkey.appthemehelper.R.color.md_white_1000
+ )
+ ).toBitmap()
+ )
+
+ pushUpdate(service.applicationContext, appWidgetIds, appWidgetView)
+ }
+
+ companion object {
+
+ const val NAME: String = "app_widget_text"
+
+ private var mInstance: AppWidgetText? = null
+
+ val instance: AppWidgetText
+ @Synchronized get() {
+ if (mInstance == null) {
+ mInstance = AppWidgetText()
+ }
+ return mInstance!!
+ }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/BootReceiver.java b/app/src/main/java/code/name/monkey/retromusic/appwidgets/BootReceiver.java
deleted file mode 100644
index 38fb29895..000000000
--- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/BootReceiver.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package code.name.monkey.retromusic.appwidgets;
-
-import android.appwidget.AppWidgetManager;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Build;
-
-import code.name.monkey.retromusic.service.MusicService;
-
-
-/**
- * @author Eugene Cheung (arkon)
- */
-public class BootReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- final AppWidgetManager widgetManager = AppWidgetManager.getInstance(context);
-
- // Start music service if there are any existing widgets
- if (widgetManager.getAppWidgetIds(new ComponentName(context, AppWidgetBig.class)).length > 0 ||
- widgetManager.getAppWidgetIds(new ComponentName(context, AppWidgetClassic.class)).length > 0 ||
- widgetManager.getAppWidgetIds(new ComponentName(context, AppWidgetSmall.class)).length > 0 ||
- widgetManager.getAppWidgetIds(new ComponentName(context, AppWidgetCard.class)).length > 0) {
- final Intent serviceIntent = new Intent(context, MusicService.class);
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { // not allowed on Oreo
- context.startService(serviceIntent);
- }
- }
- }
-}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/BootReceiver.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/BootReceiver.kt
new file mode 100644
index 000000000..079e90ae6
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/BootReceiver.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.appwidgets
+
+import android.appwidget.AppWidgetManager
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import code.name.monkey.retromusic.service.MusicService
+
+class BootReceiver : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ val widgetManager = AppWidgetManager.getInstance(context)
+
+ // Start music service if there are any existing widgets
+ if (widgetManager.getAppWidgetIds(
+ ComponentName(
+ context, AppWidgetBig::class.java
+ )
+ ).isNotEmpty() || widgetManager.getAppWidgetIds(
+ ComponentName(
+ context, AppWidgetClassic::class.java
+ )
+ ).isNotEmpty() || widgetManager.getAppWidgetIds(
+ ComponentName(
+ context, AppWidgetSmall::class.java
+ )
+ ).isNotEmpty() || widgetManager.getAppWidgetIds(
+ ComponentName(
+ context, AppWidgetCard::class.java
+ )
+ ).isNotEmpty()
+ ) {
+ val serviceIntent = Intent(context, MusicService::class.java)
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { // not allowed on Oreo
+ context.startService(serviceIntent)
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/base/BaseAppWidget.java b/app/src/main/java/code/name/monkey/retromusic/appwidgets/base/BaseAppWidget.java
deleted file mode 100644
index b2944cb44..000000000
--- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/base/BaseAppWidget.java
+++ /dev/null
@@ -1,162 +0,0 @@
-package code.name.monkey.retromusic.appwidgets.base;
-
-import android.app.PendingIntent;
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProvider;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapShader;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.RectF;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.text.TextUtils;
-import android.widget.RemoteViews;
-import code.name.monkey.retromusic.Constants;
-import code.name.monkey.retromusic.R;
-import code.name.monkey.retromusic.model.Song;
-import code.name.monkey.retromusic.service.MusicService;
-
-public abstract class BaseAppWidget extends AppWidgetProvider {
-
- public static final String NAME = "app_widget";
-
- protected static Bitmap createBitmap(Drawable drawable, float sizeMultiplier) {
- Bitmap bitmap = Bitmap.createBitmap((int) (drawable.getIntrinsicWidth() * sizeMultiplier),
- (int) (drawable.getIntrinsicHeight() * sizeMultiplier), Bitmap.Config.ARGB_8888);
- Canvas c = new Canvas(bitmap);
- drawable.setBounds(0, 0, c.getWidth(), c.getHeight());
- drawable.draw(c);
- return bitmap;
- }
-
- protected static Bitmap createRoundedBitmap(Drawable drawable, int width, int height, float tl,
- float tr, float bl, float br) {
- if (drawable == null) {
- return null;
- }
-
- Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- Canvas c = new Canvas(bitmap);
- drawable.setBounds(0, 0, width, height);
- drawable.draw(c);
-
- Bitmap rounded = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-
- Canvas canvas = new Canvas(rounded);
- Paint paint = new Paint();
- paint.setShader(
- new BitmapShader(bitmap, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP));
- paint.setAntiAlias(true);
- canvas.drawPath(composeRoundedRectPath(new RectF(0, 0, width, height), tl, tr, bl, br), paint);
-
- return rounded;
- }
-
- protected static Path composeRoundedRectPath(RectF rect, float tl, float tr, float bl, float br) {
- Path path = new Path();
- tl = tl < 0 ? 0 : tl;
- tr = tr < 0 ? 0 : tr;
- bl = bl < 0 ? 0 : bl;
- br = br < 0 ? 0 : br;
-
- path.moveTo(rect.left + tl, rect.top);
- path.lineTo(rect.right - tr, rect.top);
- path.quadTo(rect.right, rect.top, rect.right, rect.top + tr);
- path.lineTo(rect.right, rect.bottom - br);
- path.quadTo(rect.right, rect.bottom, rect.right - br, rect.bottom);
- path.lineTo(rect.left + bl, rect.bottom);
- path.quadTo(rect.left, rect.bottom, rect.left, rect.bottom - bl);
- path.lineTo(rect.left, rect.top + tl);
- path.quadTo(rect.left, rect.top, rect.left + tl, rect.top);
- path.close();
-
- return path;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onUpdate(final Context context, final AppWidgetManager appWidgetManager,
- final int[] appWidgetIds) {
- defaultAppWidget(context, appWidgetIds);
- final Intent updateIntent = new Intent(Constants.APP_WIDGET_UPDATE);
- updateIntent.putExtra(Constants.EXTRA_APP_WIDGET_NAME, NAME);
- updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
- updateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- context.sendBroadcast(updateIntent);
- }
-
- /**
- * Handle a change notification coming over from {@link MusicService}
- */
- public void notifyChange(final MusicService service, final String what) {
- if (hasInstances(service)) {
- if (Constants.META_CHANGED.equals(what) || Constants.PLAY_STATE_CHANGED.equals(what)) {
- performUpdate(service, null);
- }
- }
- }
-
- protected void pushUpdate(final Context context, final int[] appWidgetIds,
- final RemoteViews views) {
- final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
- if (appWidgetIds != null) {
- appWidgetManager.updateAppWidget(appWidgetIds, views);
- } else {
- appWidgetManager.updateAppWidget(new ComponentName(context, getClass()), views);
- }
- }
-
- /**
- * Check against {@link AppWidgetManager} if there are any instances of this widget.
- */
- protected boolean hasInstances(final Context context) {
- final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
- final int[] mAppWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context,
- getClass()));
- return mAppWidgetIds.length > 0;
- }
-
- protected PendingIntent buildPendingIntent(Context context, final String action,
- final ComponentName serviceName) {
- Intent intent = new Intent(action);
- intent.setComponent(serviceName);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- return PendingIntent.getForegroundService(context, 0, intent, 0);
- } else {
- return PendingIntent.getService(context, 0, intent, 0);
- }
- }
-
- abstract protected void defaultAppWidget(final Context context, final int[] appWidgetIds);
-
- abstract public void performUpdate(final MusicService service, final int[] appWidgetIds);
-
- protected Drawable getAlbumArtDrawable(final Resources resources, final Bitmap bitmap) {
- Drawable image;
- if (bitmap == null) {
- image = resources.getDrawable(R.drawable.default_album_art);
- } else {
- image = new BitmapDrawable(resources, bitmap);
- }
- return image;
- }
-
- protected String getSongArtistAndAlbum(final Song song) {
- final StringBuilder builder = new StringBuilder();
- builder.append(song.artistName);
- if (!TextUtils.isEmpty(song.artistName) && !TextUtils.isEmpty(song.albumName)) {
- builder.append(" • ");
- }
- builder.append(song.albumName);
- return builder.toString();
- }
-}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/base/BaseAppWidget.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/base/BaseAppWidget.kt
new file mode 100644
index 000000000..e460b7956
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/base/BaseAppWidget.kt
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.appwidgets.base
+
+import android.app.PendingIntent
+import android.appwidget.AppWidgetManager
+import android.appwidget.AppWidgetProvider
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.*
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.widget.RemoteViews
+import androidx.core.content.ContextCompat
+import code.name.monkey.appthemehelper.util.VersionUtils
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.model.Song
+import code.name.monkey.retromusic.service.MusicService
+import code.name.monkey.retromusic.service.MusicService.Companion.APP_WIDGET_UPDATE
+import code.name.monkey.retromusic.service.MusicService.Companion.EXTRA_APP_WIDGET_NAME
+import code.name.monkey.retromusic.service.MusicService.Companion.FAVORITE_STATE_CHANGED
+import code.name.monkey.retromusic.service.MusicService.Companion.META_CHANGED
+import code.name.monkey.retromusic.service.MusicService.Companion.PLAY_STATE_CHANGED
+
+abstract class BaseAppWidget : AppWidgetProvider() {
+
+ /**
+ * {@inheritDoc}
+ */
+ override fun onUpdate(
+ context: Context,
+ appWidgetManager: AppWidgetManager,
+ appWidgetIds: IntArray
+ ) {
+ defaultAppWidget(context, appWidgetIds)
+ val updateIntent = Intent(APP_WIDGET_UPDATE)
+ updateIntent.putExtra(EXTRA_APP_WIDGET_NAME, NAME)
+ updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds)
+ updateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
+ context.sendBroadcast(updateIntent)
+ }
+
+ /**
+ * Handle a change notification coming over from [MusicService]
+ */
+ fun notifyChange(service: MusicService, what: String) {
+ if (hasInstances(service)) {
+ if (META_CHANGED == what || PLAY_STATE_CHANGED == what || FAVORITE_STATE_CHANGED == what) {
+ performUpdate(service, null)
+ }
+ }
+ }
+
+ protected fun pushUpdate(
+ context: Context,
+ appWidgetIds: IntArray?,
+ views: RemoteViews
+ ) {
+ val appWidgetManager = AppWidgetManager.getInstance(context)
+ if (appWidgetIds != null) {
+ appWidgetManager.updateAppWidget(appWidgetIds, views)
+ } else {
+ appWidgetManager.updateAppWidget(ComponentName(context, javaClass), views)
+ }
+ }
+
+ /**
+ * Check against [AppWidgetManager] if there are any instances of this widget.
+ */
+ private fun hasInstances(context: Context): Boolean {
+ val appWidgetManager = AppWidgetManager.getInstance(context)
+ val mAppWidgetIds = appWidgetManager.getAppWidgetIds(
+ ComponentName(
+ context, javaClass
+ )
+ )
+ return mAppWidgetIds.isNotEmpty()
+ }
+
+ protected fun buildPendingIntent(
+ context: Context,
+ action: String,
+ serviceName: ComponentName
+ ): PendingIntent {
+ val intent = Intent(action)
+ intent.component = serviceName
+ return if (VersionUtils.hasOreo()) {
+ PendingIntent.getForegroundService(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
+ } else {
+ PendingIntent.getService(
+ context, 0, intent, if (VersionUtils.hasMarshmallow())
+ PendingIntent.FLAG_IMMUTABLE
+ else 0
+ )
+ }
+ }
+
+ protected abstract fun defaultAppWidget(context: Context, appWidgetIds: IntArray)
+
+ abstract fun performUpdate(service: MusicService, appWidgetIds: IntArray?)
+
+ protected fun getAlbumArtDrawable(context: Context, bitmap: Bitmap?): Drawable {
+ return if (bitmap == null) {
+ ContextCompat.getDrawable(context, R.drawable.default_audio_art)!!
+ } else {
+ BitmapDrawable(context.resources, bitmap)
+ }
+ }
+
+ protected fun getSongArtistAndAlbum(song: Song): String {
+ val builder = StringBuilder()
+ builder.append(song.artistName)
+ if (song.artistName.isNotEmpty() && song.albumName.isNotEmpty()) {
+ builder.append(" • ")
+ }
+ builder.append(song.albumName)
+ return builder.toString()
+ }
+
+ companion object {
+
+ const val NAME: String = "app_widget"
+
+ fun createRoundedBitmap(
+ drawable: Drawable?,
+ width: Int,
+ height: Int,
+ tl: Float,
+ tr: Float,
+ bl: Float,
+ br: Float
+ ): Bitmap? {
+ if (drawable == null) {
+ return null
+ }
+
+ val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
+ val c = Canvas(bitmap)
+ drawable.setBounds(0, 0, width, height)
+ drawable.draw(c)
+
+ val rounded = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
+
+ val canvas = Canvas(rounded)
+ val paint = Paint()
+ paint.shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
+ paint.isAntiAlias = true
+ canvas.drawPath(
+ composeRoundedRectPath(
+ RectF(0f, 0f, width.toFloat(), height.toFloat()), tl, tr, bl, br
+ ), paint
+ )
+
+ return rounded
+ }
+
+ protected fun composeRoundedRectPath(
+ rect: RectF,
+ tl: Float,
+ tr: Float,
+ bl: Float,
+ br: Float
+ ): Path {
+ val path = Path()
+ path.moveTo(rect.left + tl, rect.top)
+ path.lineTo(rect.right - tr, rect.top)
+ path.quadTo(rect.right, rect.top, rect.right, rect.top + tr)
+ path.lineTo(rect.right, rect.bottom - br)
+ path.quadTo(rect.right, rect.bottom, rect.right - br, rect.bottom)
+ path.lineTo(rect.left + bl, rect.bottom)
+ path.quadTo(rect.left, rect.bottom, rect.left, rect.bottom - bl)
+ path.lineTo(rect.left, rect.top + tl)
+ path.quadTo(rect.left, rect.top, rect.left + tl, rect.top)
+ path.close()
+
+ return path
+ }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/auto/AutoMediaIDHelper.java b/app/src/main/java/code/name/monkey/retromusic/auto/AutoMediaIDHelper.java
new file mode 100644
index 000000000..3d1f4815d
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/auto/AutoMediaIDHelper.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2019 Hemanth Savarala.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by
+ * the Free Software Foundation either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ */
+
+package code.name.monkey.retromusic.auto;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Created by Beesham Sarendranauth (Beesham)
+ */
+public class AutoMediaIDHelper {
+
+ // Media IDs used on browseable items of MediaBrowser
+ public static final String MEDIA_ID_EMPTY_ROOT = "__EMPTY_ROOT__";
+ public static final String MEDIA_ID_ROOT = "__ROOT__";
+ public static final String MEDIA_ID_MUSICS_BY_SEARCH = "__BY_SEARCH__"; // TODO
+ public static final String MEDIA_ID_MUSICS_BY_HISTORY = "__BY_HISTORY__";
+ public static final String MEDIA_ID_MUSICS_BY_TOP_TRACKS = "__BY_TOP_TRACKS__";
+ public static final String MEDIA_ID_MUSICS_BY_SUGGESTIONS = "__BY_SUGGESTIONS__";
+ public static final String MEDIA_ID_MUSICS_BY_PLAYLIST = "__BY_PLAYLIST__";
+ public static final String MEDIA_ID_MUSICS_BY_ALBUM = "__BY_ALBUM__";
+ public static final String MEDIA_ID_MUSICS_BY_ARTIST = "__BY_ARTIST__";
+ public static final String MEDIA_ID_MUSICS_BY_ALBUM_ARTIST = "__BY_ALBUM_ARTIST__";
+ public static final String MEDIA_ID_MUSICS_BY_GENRE = "__BY_GENRE__";
+ public static final String MEDIA_ID_MUSICS_BY_SHUFFLE = "__BY_SHUFFLE__";
+ public static final String MEDIA_ID_MUSICS_BY_QUEUE = "__BY_QUEUE__";
+ public static final String RECENT_ROOT = "__RECENT__";
+
+ private static final String CATEGORY_SEPARATOR = "__/__";
+ private static final String LEAF_SEPARATOR = "__|__";
+
+ /**
+ * Create a String value that represents a playable or a browsable media.
+ *
+ * Encode the media browseable categories, if any, and the unique music ID, if any,
+ * into a single String mediaID.
+ *
+ * MediaIDs are of the form __/____|__, to make it
+ * easy to find the category (like genre) that a music was selected from, so we
+ * can correctly build the playing queue. This is specially useful when
+ * one music can appear in more than one list, like "by genre -> genre_1"
+ * and "by artist -> artist_1".
+ *
+ * @param mediaID Unique ID for playable items, or null for browseable items.
+ * @param categories Hierarchy of categories representing this item's browsing parents.
+ * @return A hierarchy-aware media ID.
+ */
+ public static String createMediaID(String mediaID, String... categories) {
+ StringBuilder sb = new StringBuilder();
+ if (categories != null) {
+ for (int i = 0; i < categories.length; i++) {
+ if (!isValidCategory(categories[i])) {
+ throw new IllegalArgumentException("Invalid category: " + categories[i]);
+ }
+ sb.append(categories[i]);
+ if (i < categories.length - 1) {
+ sb.append(CATEGORY_SEPARATOR);
+ }
+ }
+ }
+ if (mediaID != null) {
+ sb.append(LEAF_SEPARATOR).append(mediaID);
+ }
+ return sb.toString();
+ }
+
+ public static String extractCategory(@NonNull String mediaID) {
+ int pos = mediaID.indexOf(LEAF_SEPARATOR);
+ if (pos >= 0) {
+ return mediaID.substring(0, pos);
+ }
+ return mediaID;
+ }
+
+ public static String extractMusicID(@NonNull String mediaID) {
+ int pos = mediaID.indexOf(LEAF_SEPARATOR);
+ if (pos >= 0) {
+ return mediaID.substring(pos + LEAF_SEPARATOR.length());
+ }
+ return null;
+ }
+
+ public static boolean isBrowseable(@NonNull String mediaID) {
+ return !mediaID.contains(LEAF_SEPARATOR);
+ }
+
+ private static boolean isValidCategory(String category) {
+ return category == null ||
+ (!category.contains(CATEGORY_SEPARATOR) && !category.contains(LEAF_SEPARATOR));
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/auto/AutoMusicProvider.kt b/app/src/main/java/code/name/monkey/retromusic/auto/AutoMusicProvider.kt
new file mode 100644
index 000000000..b6e267ed2
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/auto/AutoMusicProvider.kt
@@ -0,0 +1,283 @@
+/*
+ * Copyright (c) 2019 Hemanth Savarala.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by
+ * the Free Software Foundation either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ */
+package code.name.monkey.retromusic.auto
+
+import android.content.Context
+import android.content.res.Resources
+import android.support.v4.media.MediaBrowserCompat
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.helper.MusicPlayerRemote
+import code.name.monkey.retromusic.model.CategoryInfo
+import code.name.monkey.retromusic.model.Song
+import code.name.monkey.retromusic.repository.*
+import code.name.monkey.retromusic.service.MusicService
+import code.name.monkey.retromusic.util.MusicUtil
+import code.name.monkey.retromusic.util.PreferenceUtil
+import java.lang.ref.WeakReference
+
+
+/**
+ * Created by Beesham Sarendranauth (Beesham)
+ */
+class AutoMusicProvider(
+ private val mContext: Context,
+ private val songsRepository: SongRepository,
+ private val albumsRepository: AlbumRepository,
+ private val artistsRepository: ArtistRepository,
+ private val genresRepository: GenreRepository,
+ private val playlistsRepository: PlaylistRepository,
+ private val topPlayedRepository: TopPlayedRepository
+) {
+ private var mMusicService: WeakReference? = null
+
+ fun setMusicService(service: MusicService) {
+ mMusicService = WeakReference(service)
+ }
+
+ fun getChildren(mediaId: String?, resources: Resources): List {
+ val mediaItems: MutableList = ArrayList()
+ when (mediaId) {
+ AutoMediaIDHelper.MEDIA_ID_ROOT -> {
+ mediaItems.addAll(getRootChildren(resources))
+ }
+ AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_PLAYLIST -> for (playlist in playlistsRepository.playlists()) {
+ mediaItems.add(
+ AutoMediaItem.with(mContext)
+ .path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_PLAYLIST, playlist.id)
+ .icon(R.drawable.ic_playlist_play)
+ .title(playlist.name)
+ .subTitle(playlist.getInfoString(mContext))
+ .asPlayable()
+ .build()
+ )
+ }
+ AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM -> for (album in albumsRepository.albums()) {
+ mediaItems.add(
+ AutoMediaItem.with(mContext)
+ .path(mediaId, album.id)
+ .title(album.title)
+ .subTitle(album.albumArtist ?: album.artistName)
+ .icon(MusicUtil.getMediaStoreAlbumCoverUri(album.id))
+ .asPlayable()
+ .build()
+ )
+ }
+ AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ARTIST -> for (artist in artistsRepository.artists()) {
+ mediaItems.add(
+ AutoMediaItem.with(mContext)
+ .asPlayable()
+ .path(mediaId, artist.id)
+ .title(artist.name)
+ .build()
+ )
+ }
+ AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM_ARTIST -> for (artist in artistsRepository.albumArtists()) {
+ mediaItems.add(
+ AutoMediaItem.with(mContext)
+ .asPlayable()
+ // we just pass album id here as we don't have album artist id's
+ .path(mediaId, artist.safeGetFirstAlbum().id)
+ .title(artist.name)
+ .build()
+ )
+ }
+ AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_GENRE -> for (genre in genresRepository.genres()) {
+ mediaItems.add(
+ AutoMediaItem.with(mContext)
+ .asPlayable()
+ .path(mediaId, genre.id)
+ .title(genre.name)
+ .build()
+ )
+ }
+ AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_QUEUE ->
+ mMusicService?.get()?.playingQueue
+ ?.let {
+ for (song in it) {
+ mediaItems.add(
+ AutoMediaItem.with(mContext)
+ .asPlayable()
+ .path(mediaId, song.id)
+ .title(song.title)
+ .subTitle(song.artistName)
+ .icon(MusicUtil.getMediaStoreAlbumCoverUri(song.albumId))
+ .build()
+ )
+ }
+ }
+ else -> {
+ getPlaylistChildren(mediaId, mediaItems)
+ }
+ }
+ return mediaItems
+ }
+
+ private fun getPlaylistChildren(
+ mediaId: String?,
+ mediaItems: MutableList
+ ) {
+ val songs = when (mediaId) {
+ AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_TOP_TRACKS -> {
+ topPlayedRepository.topTracks()
+ }
+ AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_HISTORY -> {
+ topPlayedRepository.recentlyPlayedTracks()
+ }
+ AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_SUGGESTIONS -> {
+ topPlayedRepository.notRecentlyPlayedTracks().take(8)
+ }
+ else -> {
+ emptyList()
+ }
+ }
+ songs.forEach { song ->
+ mediaItems.add(
+ getPlayableSong(mediaId, song)
+ )
+ }
+ }
+
+ private fun getRootChildren(resources: Resources): List {
+ val mediaItems: MutableList = ArrayList()
+ val libraryCategories = PreferenceUtil.libraryCategory
+ libraryCategories.forEach {
+ if (it.visible) {
+ when (it.category) {
+ CategoryInfo.Category.Albums -> {
+ mediaItems.add(
+ AutoMediaItem.with(mContext)
+ .asBrowsable()
+ .path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM)
+ .gridLayout(true)
+ .icon(R.drawable.ic_album)
+ .title(resources.getString(R.string.albums)).build()
+ )
+ }
+ CategoryInfo.Category.Artists -> {
+ if (PreferenceUtil.albumArtistsOnly) {
+ mediaItems.add(
+ AutoMediaItem.with(mContext)
+ .asBrowsable()
+ .path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM_ARTIST)
+ .icon(R.drawable.ic_album_artist)
+ .title(resources.getString(R.string.album_artist)).build()
+ )
+ } else {
+ mediaItems.add(
+ AutoMediaItem.with(mContext)
+ .asBrowsable()
+ .path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ARTIST)
+ .icon(R.drawable.ic_artist)
+ .title(resources.getString(R.string.artists)).build()
+ )
+ }
+ }
+ CategoryInfo.Category.Genres -> {
+ mediaItems.add(
+ AutoMediaItem.with(mContext)
+ .asBrowsable()
+ .path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_GENRE)
+ .icon(R.drawable.ic_guitar)
+ .title(resources.getString(R.string.genres)).build()
+ )
+ }
+ CategoryInfo.Category.Playlists -> {
+ mediaItems.add(
+ AutoMediaItem.with(mContext)
+ .asBrowsable()
+ .path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_PLAYLIST)
+ .icon(R.drawable.ic_playlist_play)
+ .title(resources.getString(R.string.playlists)).build()
+ )
+ }
+ else -> {
+ }
+ }
+ }
+ }
+ mediaItems.add(
+ AutoMediaItem.with(mContext)
+ .asPlayable()
+ .path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_SHUFFLE)
+ .icon(R.drawable.ic_shuffle)
+ .title(resources.getString(R.string.action_shuffle_all))
+ .subTitle(MusicUtil.getPlaylistInfoString(mContext, songsRepository.songs()))
+ .build()
+ )
+ mediaItems.add(
+ AutoMediaItem.with(mContext)
+ .asBrowsable()
+ .path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_QUEUE)
+ .icon(R.drawable.ic_queue_music)
+ .title(resources.getString(R.string.queue))
+ .subTitle(MusicUtil.getPlaylistInfoString(mContext, MusicPlayerRemote.playingQueue))
+ .asBrowsable().build()
+ )
+ mediaItems.add(
+ AutoMediaItem.with(mContext)
+ .asBrowsable()
+ .path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_TOP_TRACKS)
+ .icon(R.drawable.ic_trending_up)
+ .title(resources.getString(R.string.my_top_tracks))
+ .subTitle(
+ MusicUtil.getPlaylistInfoString(
+ mContext,
+ topPlayedRepository.topTracks()
+ )
+ )
+ .asBrowsable().build()
+ )
+ mediaItems.add(
+ AutoMediaItem.with(mContext)
+ .asBrowsable()
+ .path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_SUGGESTIONS)
+ .icon(R.drawable.ic_face)
+ .title(resources.getString(R.string.suggestion_songs))
+ .subTitle(
+ MusicUtil.getPlaylistInfoString(
+ mContext,
+ topPlayedRepository.notRecentlyPlayedTracks().takeIf {
+ it.size > 9
+ } ?: emptyList()
+ )
+ )
+ .asBrowsable().build()
+ )
+ mediaItems.add(
+ AutoMediaItem.with(mContext)
+ .asBrowsable()
+ .path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_HISTORY)
+ .icon(R.drawable.ic_history)
+ .title(resources.getString(R.string.history))
+ .subTitle(
+ MusicUtil.getPlaylistInfoString(
+ mContext,
+ topPlayedRepository.recentlyPlayedTracks()
+ )
+ )
+ .asBrowsable().build()
+ )
+ return mediaItems
+ }
+
+ private fun getPlayableSong(mediaId: String?, song: Song): MediaBrowserCompat.MediaItem {
+ return AutoMediaItem.with(mContext)
+ .asPlayable()
+ .path(mediaId, song.id)
+ .title(song.title)
+ .subTitle(song.artistName)
+ .icon(MusicUtil.getMediaStoreAlbumCoverUri(song.albumId))
+ .build()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/auto/MediaItemBuilder.kt b/app/src/main/java/code/name/monkey/retromusic/auto/MediaItemBuilder.kt
new file mode 100644
index 000000000..534b0ec52
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/auto/MediaItemBuilder.kt
@@ -0,0 +1,100 @@
+package code.name.monkey.retromusic.auto
+
+import android.content.Context
+import android.net.Uri
+import android.support.v4.media.MediaBrowserCompat
+import android.support.v4.media.MediaDescriptionCompat
+import androidx.core.content.res.ResourcesCompat
+import androidx.core.graphics.drawable.toBitmap
+import androidx.core.os.bundleOf
+
+
+internal object AutoMediaItem {
+ fun with(context: Context): Builder {
+ return Builder(context)
+ }
+
+ internal class Builder(private val mContext: Context) {
+ private var mBuilder: MediaDescriptionCompat.Builder?
+ private var mFlags = 0
+ fun path(fullPath: String): Builder {
+ mBuilder?.setMediaId(fullPath)
+ return this
+ }
+
+ fun path(path: String?, id: Long): Builder {
+ return path(AutoMediaIDHelper.createMediaID(id.toString(), path))
+ }
+
+ fun title(title: String): Builder {
+ mBuilder?.setTitle(title)
+ return this
+ }
+
+ fun subTitle(subTitle: String): Builder {
+ mBuilder?.setSubtitle(subTitle)
+ return this
+ }
+
+ fun icon(uri: Uri?): Builder {
+ mBuilder?.setIconUri(uri)
+ return this
+ }
+
+ fun icon(iconDrawableId: Int): Builder {
+ mBuilder?.setIconBitmap(
+ ResourcesCompat.getDrawable(
+ mContext.resources,
+ iconDrawableId,
+ mContext.theme
+ )?.toBitmap()
+ )
+ return this
+ }
+
+ fun gridLayout(isGrid: Boolean): Builder {
+
+ val hints = bundleOf(
+ CONTENT_STYLE_SUPPORTED to true,
+ CONTENT_STYLE_BROWSABLE_HINT to
+ if (isGrid) CONTENT_STYLE_GRID_ITEM_HINT_VALUE
+ else CONTENT_STYLE_LIST_ITEM_HINT_VALUE,
+ CONTENT_STYLE_PLAYABLE_HINT to
+ if (isGrid) CONTENT_STYLE_GRID_ITEM_HINT_VALUE
+ else CONTENT_STYLE_LIST_ITEM_HINT_VALUE
+ )
+ mBuilder?.setExtras(hints)
+ return this
+ }
+
+ fun asBrowsable(): Builder {
+ mFlags = mFlags or MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
+ return this
+ }
+
+ fun asPlayable(): Builder {
+ mFlags = mFlags or MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
+ return this
+ }
+
+ fun build(): MediaBrowserCompat.MediaItem {
+ val result = MediaBrowserCompat.MediaItem(mBuilder!!.build(), mFlags)
+ mBuilder = null
+ mFlags = 0
+ return result
+ }
+
+ init {
+ mBuilder = MediaDescriptionCompat.Builder()
+ }
+ companion object{
+ // Hints - see https://developer.android.com/training/cars/media#default-content-style
+ const val CONTENT_STYLE_SUPPORTED = "android.media.browse.CONTENT_STYLE_SUPPORTED"
+ const val CONTENT_STYLE_BROWSABLE_HINT = "android.media.browse.CONTENT_STYLE_BROWSABLE_HINT"
+ const val CONTENT_STYLE_PLAYABLE_HINT = "android.media.browse.CONTENT_STYLE_PLAYABLE_HINT"
+ const val CONTENT_STYLE_LIST_ITEM_HINT_VALUE = 1
+ const val CONTENT_STYLE_GRID_ITEM_HINT_VALUE = 2
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/db/BlackListStoreDao.kt b/app/src/main/java/code/name/monkey/retromusic/db/BlackListStoreDao.kt
new file mode 100644
index 000000000..0bf40b736
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/db/BlackListStoreDao.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.db
+
+import androidx.room.*
+
+@Dao
+interface BlackListStoreDao {
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ fun insertBlacklistPath(blackListStoreEntity: BlackListStoreEntity)
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insertBlacklistPath(blackListStoreEntities: List)
+
+ @Delete
+ suspend fun deleteBlacklistPath(blackListStoreEntity: BlackListStoreEntity)
+
+ @Query("DELETE FROM BlackListStoreEntity")
+ suspend fun clearBlacklist()
+
+ @Query("SELECT * FROM BlackListStoreEntity")
+ fun blackListPaths(): List
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/db/BlackListStoreEntity.kt b/app/src/main/java/code/name/monkey/retromusic/db/BlackListStoreEntity.kt
new file mode 100644
index 000000000..8592442a2
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/db/BlackListStoreEntity.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.db
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity
+class BlackListStoreEntity(
+ @PrimaryKey
+ val path: String
+)
diff --git a/app/src/main/java/code/name/monkey/retromusic/db/HistoryDao.kt b/app/src/main/java/code/name/monkey/retromusic/db/HistoryDao.kt
new file mode 100644
index 000000000..2ceb309db
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/db/HistoryDao.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.db
+
+import androidx.lifecycle.LiveData
+import androidx.room.*
+
+@Dao
+interface HistoryDao {
+ companion object {
+ private const val HISTORY_LIMIT = 100
+ }
+
+ @Upsert
+ suspend fun upsertSongInHistory(historyEntity: HistoryEntity)
+
+ @Query("DELETE FROM HistoryEntity WHERE id= :songId")
+ fun deleteSongInHistory(songId: Long)
+
+ @Query("SELECT * FROM HistoryEntity ORDER BY time_played DESC LIMIT $HISTORY_LIMIT")
+ fun historySongs(): List
+
+ @Query("SELECT * FROM HistoryEntity ORDER BY time_played DESC LIMIT $HISTORY_LIMIT")
+ fun observableHistorySongs(): LiveData>
+
+ @Query("DELETE FROM HistoryEntity")
+ suspend fun clearHistory()
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/db/HistoryEntity.kt b/app/src/main/java/code/name/monkey/retromusic/db/HistoryEntity.kt
new file mode 100644
index 000000000..535a37964
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/db/HistoryEntity.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.db
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity
+class HistoryEntity(
+ @PrimaryKey
+ val id: Long,
+ val title: String,
+ @ColumnInfo(name = "track_number")
+ val trackNumber: Int,
+ val year: Int,
+ val duration: Long,
+ val data: String,
+ @ColumnInfo(name = "date_modified")
+ val dateModified: Long,
+ @ColumnInfo(name = "album_id")
+ val albumId: Long,
+ @ColumnInfo(name = "album_name")
+ val albumName: String,
+ @ColumnInfo(name = "artist_id")
+ val artistId: Long,
+ @ColumnInfo(name = "artist_name")
+ val artistName: String,
+ val composer: String?,
+ @ColumnInfo(name = "album_artist")
+ val albumArtist: String?,
+ @ColumnInfo(name = "time_played")
+ val timePlayed: Long
+)
diff --git a/app/src/main/java/code/name/monkey/retromusic/db/LyricsDao.kt b/app/src/main/java/code/name/monkey/retromusic/db/LyricsDao.kt
new file mode 100644
index 000000000..fa14b1aca
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/db/LyricsDao.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.db
+
+import androidx.room.*
+
+@Dao
+interface LyricsDao {
+ @Query("SELECT * FROM LyricsEntity WHERE songId =:songId LIMIT 1")
+ fun lyricsWithSongId(songId: Int): LyricsEntity?
+
+ @Insert
+ fun insertLyrics(lyricsEntity: LyricsEntity)
+
+ @Delete
+ fun deleteLyrics(lyricsEntity: LyricsEntity)
+
+ @Update
+ fun updateLyrics(lyricsEntity: LyricsEntity)
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/db/LyricsEntity.kt b/app/src/main/java/code/name/monkey/retromusic/db/LyricsEntity.kt
new file mode 100644
index 000000000..91d987d62
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/db/LyricsEntity.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.db
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity
+class LyricsEntity(
+ @PrimaryKey val songId: Int,
+ val lyrics: String
+)
diff --git a/app/src/main/java/code/name/monkey/retromusic/db/PlayCountDao.kt b/app/src/main/java/code/name/monkey/retromusic/db/PlayCountDao.kt
new file mode 100644
index 000000000..087ae4faf
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/db/PlayCountDao.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.db
+
+import androidx.room.*
+
+@Dao
+interface PlayCountDao {
+
+ @Upsert
+ fun upsertSongInPlayCount(playCountEntity: PlayCountEntity)
+
+ @Delete
+ fun deleteSongInPlayCount(playCountEntity: PlayCountEntity)
+
+ @Query("SELECT * FROM PlayCountEntity WHERE id =:songId LIMIT 1")
+ fun findSongExistInPlayCount(songId: Long): PlayCountEntity?
+
+ @Query("SELECT * FROM PlayCountEntity ORDER BY play_count DESC")
+ fun playCountSongs(): List
+
+ @Query("DELETE FROM SongEntity WHERE id =:songId")
+ fun deleteSong(songId: Long)
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/db/PlayCountEntity.kt b/app/src/main/java/code/name/monkey/retromusic/db/PlayCountEntity.kt
new file mode 100644
index 000000000..2fa41b227
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/db/PlayCountEntity.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.db
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity
+class PlayCountEntity(
+ @PrimaryKey
+ val id: Long,
+ val title: String,
+ @ColumnInfo(name = "track_number")
+ val trackNumber: Int,
+ val year: Int,
+ val duration: Long,
+ val data: String,
+ @ColumnInfo(name = "date_modified")
+ val dateModified: Long,
+ @ColumnInfo(name = "album_id")
+ val albumId: Long,
+ @ColumnInfo(name = "album_name")
+ val albumName: String,
+ @ColumnInfo(name = "artist_id")
+ val artistId: Long,
+ @ColumnInfo(name = "artist_name")
+ val artistName: String,
+ val composer: String?,
+ @ColumnInfo(name = "album_artist")
+ val albumArtist: String?,
+ @ColumnInfo(name = "time_played")
+ val timePlayed: Long,
+ @ColumnInfo(name = "play_count")
+ var playCount: Int
+)
diff --git a/app/src/main/java/code/name/monkey/retromusic/db/PlaylistDao.kt b/app/src/main/java/code/name/monkey/retromusic/db/PlaylistDao.kt
new file mode 100644
index 000000000..dea1e6b7d
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/db/PlaylistDao.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.db
+
+import androidx.lifecycle.LiveData
+import androidx.room.*
+
+@Dao
+interface PlaylistDao {
+ @Insert
+ suspend fun createPlaylist(playlistEntity: PlaylistEntity): Long
+
+ @Query("UPDATE PlaylistEntity SET playlist_name = :name WHERE playlist_id = :playlistId")
+ suspend fun renamePlaylist(playlistId: Long, name: String)
+
+ @Query("SELECT * FROM PlaylistEntity WHERE playlist_name = :name")
+ fun playlist(name: String): List
+
+ @Query("SELECT * FROM PlaylistEntity")
+ suspend fun playlists(): List
+
+ @Query("DELETE FROM SongEntity WHERE playlist_creator_id = :playlistId")
+ suspend fun deletePlaylistSongs(playlistId: Long)
+
+ @Query("DELETE FROM SongEntity WHERE playlist_creator_id = :playlistId AND id = :songId")
+ suspend fun deleteSongFromPlaylist(playlistId: Long, songId: Long)
+
+ @Transaction
+ @Query("SELECT * FROM PlaylistEntity")
+ suspend fun playlistsWithSongs(): List
+
+ @Transaction
+ @Query("SELECT * FROM PlaylistEntity WHERE playlist_id= :playlistId")
+ fun getPlaylist(playlistId: Long): LiveData
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insertSongsToPlaylist(songEntities: List)
+
+ @Query("SELECT * FROM SongEntity WHERE playlist_creator_id = :playlistId AND id = :songId")
+ suspend fun isSongExistsInPlaylist(playlistId: Long, songId: Long): List
+
+ @Query("SELECT * FROM SongEntity WHERE playlist_creator_id = :playlistId ORDER BY song_key asc")
+ fun songsFromPlaylist(playlistId: Long): LiveData>
+
+ @Delete
+ suspend fun deletePlaylist(playlistEntity: PlaylistEntity)
+
+ @Delete
+ suspend fun deletePlaylists(playlistEntities: List)
+
+ @Delete
+ suspend fun deletePlaylistSongs(songs: List)
+
+ @RewriteQueriesToDropUnusedColumns
+ @Query("SELECT * FROM SongEntity ,(SELECT playlist_id FROM PlaylistEntity WHERE playlist_name= :playlistName LIMIT 1) AS playlist WHERE playlist_creator_id= playlist.playlist_id")
+ fun favoritesSongsLiveData(playlistName: String): LiveData>
+
+ @Query("SELECT * FROM SongEntity WHERE playlist_creator_id= :playlistId")
+ fun favoritesSongs(playlistId: Long): List
+
+ @Query("SELECT EXISTS(SELECT * FROM PlaylistEntity WHERE playlist_id = :playlistId)")
+ fun checkPlaylistExists(playlistId: Long): LiveData
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/db/PlaylistEntity.kt b/app/src/main/java/code/name/monkey/retromusic/db/PlaylistEntity.kt
new file mode 100644
index 000000000..5493b1155
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/db/PlaylistEntity.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.db
+
+import android.os.Parcelable
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import kotlinx.parcelize.Parcelize
+
+@Entity
+@Parcelize
+class PlaylistEntity(
+ @PrimaryKey(autoGenerate = true)
+ @ColumnInfo(name = "playlist_id")
+ val playListId: Long = 0,
+ @ColumnInfo(name = "playlist_name")
+ val playlistName: String
+) : Parcelable
diff --git a/app/src/main/java/code/name/monkey/retromusic/db/PlaylistWithSongs.kt b/app/src/main/java/code/name/monkey/retromusic/db/PlaylistWithSongs.kt
new file mode 100644
index 000000000..da80d8228
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/db/PlaylistWithSongs.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.db
+
+import android.os.Parcelable
+import androidx.room.Embedded
+import androidx.room.Relation
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+data class PlaylistWithSongs(
+ @Embedded val playlistEntity: PlaylistEntity,
+ @Relation(
+ parentColumn = "playlist_id",
+ entityColumn = "playlist_creator_id"
+ )
+ val songs: List
+) : Parcelable
diff --git a/app/src/main/java/code/name/monkey/retromusic/db/RetroDatabase.kt b/app/src/main/java/code/name/monkey/retromusic/db/RetroDatabase.kt
new file mode 100644
index 000000000..6401766aa
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/db/RetroDatabase.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.db
+
+import androidx.room.Database
+import androidx.room.RoomDatabase
+
+@Database(
+ entities = [PlaylistEntity::class, SongEntity::class, HistoryEntity::class, PlayCountEntity::class],
+ version = 24,
+ exportSchema = false
+)
+abstract class RetroDatabase : RoomDatabase() {
+ abstract fun playlistDao(): PlaylistDao
+ abstract fun playCountDao(): PlayCountDao
+ abstract fun historyDao(): HistoryDao
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/db/RoomMigrations.kt b/app/src/main/java/code/name/monkey/retromusic/db/RoomMigrations.kt
new file mode 100644
index 000000000..b8b383c31
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/db/RoomMigrations.kt
@@ -0,0 +1,11 @@
+package code.name.monkey.retromusic.db
+
+import androidx.room.migration.Migration
+import androidx.sqlite.db.SupportSQLiteDatabase
+
+val MIGRATION_23_24 = object : Migration(23, 24) {
+ override fun migrate(database: SupportSQLiteDatabase) {
+ database.execSQL("DROP TABLE LyricsEntity")
+ database.execSQL("DROP TABLE BlackListStoreEntity")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/db/SongEntity.kt b/app/src/main/java/code/name/monkey/retromusic/db/SongEntity.kt
new file mode 100644
index 000000000..206c91e27
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/db/SongEntity.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.db
+
+import android.os.Parcelable
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.Index
+import androidx.room.PrimaryKey
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+@Entity(indices = [Index(value = ["playlist_creator_id", "id"], unique = true)])
+class SongEntity(
+ @PrimaryKey(autoGenerate = true)
+ @ColumnInfo(name = "song_key")
+ val songPrimaryKey: Long = 0L,
+ @ColumnInfo(name = "playlist_creator_id")
+ val playlistCreatorId: Long,
+ val id: Long,
+ val title: String,
+ @ColumnInfo(name = "track_number")
+ val trackNumber: Int,
+ val year: Int,
+ val duration: Long,
+ val data: String,
+ @ColumnInfo(name = "date_modified")
+ val dateModified: Long,
+ @ColumnInfo(name = "album_id")
+ val albumId: Long,
+ @ColumnInfo(name = "album_name")
+ val albumName: String,
+ @ColumnInfo(name = "artist_id")
+ val artistId: Long,
+ @ColumnInfo(name = "artist_name")
+ val artistName: String,
+ val composer: String?,
+ @ColumnInfo(name = "album_artist")
+ val albumArtist: String?
+) : Parcelable
diff --git a/app/src/main/java/code/name/monkey/retromusic/db/SongExtension.kt b/app/src/main/java/code/name/monkey/retromusic/db/SongExtension.kt
new file mode 100644
index 000000000..d6fe11b05
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/db/SongExtension.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.db
+
+import code.name.monkey.retromusic.model.Song
+
+fun List.fromHistoryToSongs(): List {
+ return map {
+ it.toSong()
+ }
+}
+
+fun List.toSongs(): List {
+ return map {
+ it.toSong()
+ }
+}
+
+fun Song.toHistoryEntity(timePlayed: Long): HistoryEntity {
+ return HistoryEntity(
+ id = id,
+ title = title,
+ trackNumber = trackNumber,
+ year = year,
+ duration = duration,
+ data = data,
+ dateModified = dateModified,
+ albumId = albumId,
+ albumName = albumName,
+ artistId = artistId,
+ artistName = artistName,
+ composer = composer,
+ albumArtist = albumArtist,
+ timePlayed = timePlayed
+ )
+}
+
+fun Song.toSongEntity(playListId: Long): SongEntity {
+ return SongEntity(
+ playlistCreatorId = playListId,
+ id = id,
+ title = title,
+ trackNumber = trackNumber,
+ year = year,
+ duration = duration,
+ data = data,
+ dateModified = dateModified,
+ albumId = albumId,
+ albumName = albumName,
+ artistId = artistId,
+ artistName = artistName,
+ composer = composer,
+ albumArtist = albumArtist
+ )
+}
+
+fun SongEntity.toSong(): Song {
+ return Song(
+ id = id,
+ title = title,
+ trackNumber = trackNumber,
+ year = year,
+ duration = duration,
+ data = data,
+ dateModified = dateModified,
+ albumId = albumId,
+ albumName = albumName,
+ artistId = artistId,
+ artistName = artistName,
+ composer = composer,
+ albumArtist = albumArtist
+ )
+}
+
+fun PlayCountEntity.toSong(): Song {
+ return Song(
+ id = id,
+ title = title,
+ trackNumber = trackNumber,
+ year = year,
+ duration = duration,
+ data = data,
+ dateModified = dateModified,
+ albumId = albumId,
+ albumName = albumName,
+ artistId = artistId,
+ artistName = artistName,
+ composer = composer,
+ albumArtist = albumArtist
+ )
+}
+
+fun HistoryEntity.toSong(): Song {
+ return Song(
+ id = id,
+ title = title,
+ trackNumber = trackNumber,
+ year = year,
+ duration = duration,
+ data = data,
+ dateModified = dateModified,
+ albumId = albumId,
+ albumName = albumName,
+ artistId = artistId,
+ artistName = artistName,
+ composer = composer,
+ albumArtist = albumArtist
+ )
+}
+
+fun Song.toPlayCount(): PlayCountEntity {
+ return PlayCountEntity(
+ id = id,
+ title = title,
+ trackNumber = trackNumber,
+ year = year,
+ duration = duration,
+ data = data,
+ dateModified = dateModified,
+ albumId = albumId,
+ albumName = albumName,
+ artistId = artistId,
+ artistName = artistName,
+ composer = composer,
+ albumArtist = albumArtist,
+ timePlayed = System.currentTimeMillis(),
+ playCount = 1
+ )
+}
+
+fun List.toSongsEntity(playlistEntity: PlaylistEntity): List {
+ return map {
+ it.toSongEntity(playlistEntity.playListId)
+ }
+}
+
+fun List.toSongsEntity(playlistId: Long): List {
+ return map {
+ it.toSongEntity(playlistId)
+ }
+}
\ No newline at end of file
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 bd13a4e97..000000000
--- a/app/src/main/java/code/name/monkey/retromusic/dialogs/AddToPlaylistDialog.java
+++ /dev/null
@@ -1,94 +0,0 @@
-package code.name.monkey.retromusic.dialogs;
-
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import butterknife.BindView;
-import butterknife.ButterKnife;
-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.util.PlaylistsUtil;
-import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment;
-
-/**
- * @author Karim Abou Zeid (kabouzeid), Aidan Follestad (afollestad)
- */
-public class AddToPlaylistDialog extends RoundedBottomSheetDialogFragment implements AdapterView.OnItemClickListener {
-
- @BindView(R.id.playlists)
- ListView playlist;
- @BindView(R.id.title)
- TextView title;
- List playlists;
-
- @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;
- }
-
- @Override
- public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- ArrayAdapter playlistAdapter = new ArrayAdapter<>(getActivity(), R.layout.simple_list_item);
- playlists = PlaylistLoader.getAllPlaylists(getActivity()).blockingFirst();
- playlistAdapter.add(getActivity().getResources().getString(R.string.action_new_playlist));
-
- for (int i = 1; i < playlists.size(); i++) {
- playlistAdapter.add(playlists.get(i - 1).name);
- playlistAdapter.notifyDataSetChanged();
- }
-
- this.playlist.setAdapter(playlistAdapter);
- this.playlist.setOnItemClickListener(this);
- }
-
- @Override
- public void onItemClick(AdapterView> adapterView, View view, int i, long l) {
- //noinspection unchecked
- final ArrayList songs = getArguments().getParcelableArrayList("songs");
-
- if (songs == null) {
- return;
- }
- if (i == 0) {
- dismiss();
- CreatePlaylistDialog.create(songs)
- .show(getActivity().getSupportFragmentManager(), "ADD_TO_PLAYLIST");
- } else {
- dismiss();
- PlaylistsUtil.addToPlaylist(getActivity(), songs, playlists.get(i - 1).id, true);
- }
- }
-}
\ 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 000000000..aa47f0fe2
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/AddToPlaylistDialog.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.dialogs
+
+import android.app.Dialog
+import android.os.Bundle
+import androidx.core.os.bundleOf
+import androidx.fragment.app.DialogFragment
+import code.name.monkey.retromusic.EXTRA_PLAYLISTS
+import code.name.monkey.retromusic.EXTRA_SONG
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.db.PlaylistEntity
+import code.name.monkey.retromusic.extensions.colorButtons
+import code.name.monkey.retromusic.extensions.extraNotNull
+import code.name.monkey.retromusic.extensions.materialDialog
+import code.name.monkey.retromusic.fragments.LibraryViewModel
+import code.name.monkey.retromusic.model.Song
+import org.koin.androidx.viewmodel.ext.android.activityViewModel
+
+class AddToPlaylistDialog : DialogFragment() {
+ private val libraryViewModel by activityViewModel()
+
+ companion object {
+ fun create(playlistEntities: List, song: Song): AddToPlaylistDialog {
+ val list: MutableList = mutableListOf()
+ list.add(song)
+ return create(playlistEntities, list)
+ }
+
+ fun create(playlistEntities: List, songs: List): AddToPlaylistDialog {
+ return AddToPlaylistDialog().apply {
+ arguments = bundleOf(
+ EXTRA_SONG to songs,
+ EXTRA_PLAYLISTS to playlistEntities
+ )
+ }
+ }
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val playlistEntities = extraNotNull>(EXTRA_PLAYLISTS).value
+ val songs = extraNotNull>(EXTRA_SONG).value
+ val playlistNames = mutableListOf()
+ playlistNames.add(requireContext().resources.getString(R.string.action_new_playlist))
+ for (entity: PlaylistEntity in playlistEntities) {
+ playlistNames.add(entity.playlistName)
+ }
+ return materialDialog(R.string.add_playlist_title)
+ .setItems(playlistNames.toTypedArray()) { dialog, which ->
+ if (which == 0) {
+ showCreateDialog(songs)
+ } else {
+ libraryViewModel.addToPlaylist(requireContext(), playlistNames[which], songs)
+ }
+ dialog.dismiss()
+ }
+ .setNegativeButton(R.string.action_cancel, null)
+ .create()
+ .colorButtons()
+ }
+
+ private fun showCreateDialog(songs: List) {
+ CreatePlaylistDialog.create(songs).show(requireActivity().supportFragmentManager, "Dialog")
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/BlacklistFolderChooserDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/BlacklistFolderChooserDialog.java
deleted file mode 100644
index 4602d50ba..000000000
--- a/app/src/main/java/code/name/monkey/retromusic/dialogs/BlacklistFolderChooserDialog.java
+++ /dev/null
@@ -1,159 +0,0 @@
-package code.name.monkey.retromusic.dialogs;
-
-import android.Manifest;
-import android.app.Dialog;
-import android.content.pm.PackageManager;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Environment;
-import android.support.annotation.NonNull;
-import android.support.v4.app.ActivityCompat;
-import android.support.v4.app.DialogFragment;
-import android.view.View;
-
-import com.afollestad.materialdialogs.MaterialDialog;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-import code.name.monkey.retromusic.R;
-
-/**
- * @author Aidan Follestad (afollestad), modified by Karim Abou Zeid
- */
-public class BlacklistFolderChooserDialog extends DialogFragment implements MaterialDialog.ListCallback {
-
- private File parentFolder;
- private File[] parentContents;
- private boolean canGoUp = false;
-
- private FolderCallback callback;
-
- String initialPath = Environment.getExternalStorageDirectory().getAbsolutePath();
-
- private String[] getContentsArray() {
- if (parentContents == null) {
- if (canGoUp) {
- return new String[]{".."};
- }
- return new String[]{};
- }
- String[] results = new String[parentContents.length + (canGoUp ? 1 : 0)];
- if (canGoUp) {
- results[0] = "..";
- }
- for (int i = 0; i < parentContents.length; i++) {
- results[canGoUp ? i + 1 : i] = parentContents[i].getName();
- }
- return results;
- }
-
- private File[] listFiles() {
- File[] contents = parentFolder.listFiles();
- List results = new ArrayList<>();
- if (contents != null) {
- for (File fi : contents) {
- if (fi.isDirectory()) {
- results.add(fi);
- }
- }
- Collections.sort(results, new FolderSorter());
- return results.toArray(new File[results.size()]);
- }
- return null;
- }
-
- public static BlacklistFolderChooserDialog create() {
- return new BlacklistFolderChooserDialog();
- }
-
- @NonNull
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ActivityCompat.checkSelfPermission(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE)
- != PackageManager.PERMISSION_GRANTED) {
- return new MaterialDialog.Builder(getActivity())
- .title(R.string.md_error_label)
- .content(R.string.md_storage_perm_error)
- .positiveText(android.R.string.ok)
- .build();
- }
- if (savedInstanceState == null) {
- savedInstanceState = new Bundle();
- }
- if (!savedInstanceState.containsKey("current_path")) {
- savedInstanceState.putString("current_path", initialPath);
- }
- parentFolder = new File(savedInstanceState.getString("current_path", File.pathSeparator));
- checkIfCanGoUp();
- parentContents = listFiles();
- MaterialDialog.Builder builder =
- new MaterialDialog.Builder(getActivity())
- .title(parentFolder.getAbsolutePath())
- .items((CharSequence[]) getContentsArray())
- .itemsCallback(this)
- .autoDismiss(false)
- .onPositive((dialog, which) -> {
- dismiss();
- callback.onFolderSelection(BlacklistFolderChooserDialog.this, parentFolder);
- })
- .onNegative((materialDialog, dialogAction) -> dismiss())
- .positiveText(R.string.add_action)
- .negativeText(android.R.string.cancel);
- return builder.build();
- }
-
- @Override
- public void onSelection(MaterialDialog materialDialog, View view, int i, CharSequence s) {
- if (canGoUp && i == 0) {
- parentFolder = parentFolder.getParentFile();
- if (parentFolder.getAbsolutePath().equals("/storage/emulated")) {
- parentFolder = parentFolder.getParentFile();
- }
- checkIfCanGoUp();
- } else {
- parentFolder = parentContents[canGoUp ? i - 1 : i];
- canGoUp = true;
- if (parentFolder.getAbsolutePath().equals("/storage/emulated")) {
- parentFolder = Environment.getExternalStorageDirectory();
- }
- }
- reload();
- }
-
- private void checkIfCanGoUp() {
- canGoUp = parentFolder.getParent() != null;
- }
-
- private void reload() {
- parentContents = listFiles();
- MaterialDialog dialog = (MaterialDialog) getDialog();
- dialog.setTitle(parentFolder.getAbsolutePath());
- dialog.setItems((CharSequence[]) getContentsArray());
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putString("current_path", parentFolder.getAbsolutePath());
- }
-
- public void setCallback(FolderCallback callback) {
- this.callback = callback;
- }
-
- public interface FolderCallback {
- void onFolderSelection(@NonNull BlacklistFolderChooserDialog dialog, @NonNull File folder);
- }
-
- private static class FolderSorter implements Comparator {
-
- @Override
- public int compare(File lhs, File rhs) {
- return lhs.getName().compareTo(rhs.getName());
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/BlacklistFolderChooserDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/BlacklistFolderChooserDialog.kt
new file mode 100644
index 000000000..5701d858b
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/BlacklistFolderChooserDialog.kt
@@ -0,0 +1,140 @@
+package code.name.monkey.retromusic.dialogs
+
+import android.Manifest
+import android.app.Dialog
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Bundle
+import androidx.core.app.ActivityCompat
+import androidx.fragment.app.DialogFragment
+import code.name.monkey.appthemehelper.util.VersionUtils
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.extensions.materialDialog
+import code.name.monkey.retromusic.util.getExternalStorageDirectory
+import com.afollestad.materialdialogs.MaterialDialog
+import com.afollestad.materialdialogs.list.listItems
+import com.afollestad.materialdialogs.list.updateListItems
+import java.io.File
+
+class BlacklistFolderChooserDialog : DialogFragment() {
+ private var initialPath: String = getExternalStorageDirectory().absolutePath
+ private var parentFolder: File? = null
+ private var parentContents: Array? = null
+ private var canGoUp = false
+ private var callback: FolderCallback? = null
+ private val contentsArray: Array
+ get() {
+ if (parentContents == null) {
+ return if (canGoUp) {
+ arrayOf("..")
+ } else arrayOf()
+ }
+ val results = arrayOfNulls(parentContents!!.size + if (canGoUp) 1 else 0)
+ if (canGoUp) {
+ results[0] = ".."
+ }
+ for (i in parentContents!!.indices) {
+ results[if (canGoUp) i + 1 else i] = parentContents?.getOrNull(i)?.name
+ }
+ return results
+ }
+
+ private fun listFiles(): Array? {
+ val results = mutableListOf()
+ parentFolder?.listFiles()?.let { files ->
+ files.forEach { file -> if (file.isDirectory) results.add(file) }
+ return results.sortedBy { it.name }.toTypedArray()
+ }
+ return null
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ var mSavedInstanceState = savedInstanceState
+ if (VersionUtils.hasMarshmallow()
+ && ActivityCompat.checkSelfPermission(
+ requireActivity(), Manifest.permission.READ_EXTERNAL_STORAGE
+ )
+ != PackageManager.PERMISSION_GRANTED
+ ) {
+ return materialDialog().show {
+ title(res = R.string.md_error_label)
+ message(res = R.string.md_storage_perm_error)
+ positiveButton(res = android.R.string.ok)
+ }
+ }
+ if (mSavedInstanceState == null) {
+ mSavedInstanceState = Bundle()
+ }
+ if (!mSavedInstanceState.containsKey("current_path")) {
+ mSavedInstanceState.putString("current_path", initialPath)
+ }
+ parentFolder = File(mSavedInstanceState.getString("current_path", File.pathSeparator))
+ checkIfCanGoUp()
+ parentContents = listFiles()
+ return materialDialog()
+ .title(text = parentFolder?.absolutePath)
+ .listItems(
+ items = contentsArray.toCharSequence(),
+ waitForPositiveButton = false
+ ) { _: MaterialDialog, i: Int, _: CharSequence ->
+ onSelection(i)
+ }
+ .noAutoDismiss()
+ .positiveButton(res = R.string.add_action) {
+ callback?.onFolderSelection(requireContext(), parentFolder!!)
+ dismiss()
+ }
+ .negativeButton(res = android.R.string.cancel) { dismiss() }
+ }
+
+ private fun onSelection(i: Int) {
+ if (canGoUp && i == 0) {
+ parentFolder = parentFolder?.parentFile
+ if (parentFolder?.absolutePath == "/storage/emulated") {
+ parentFolder = parentFolder?.parentFile
+ }
+ checkIfCanGoUp()
+ } else {
+ parentFolder = parentContents?.getOrNull(if (canGoUp) i - 1 else i)
+ canGoUp = true
+ if (parentFolder?.absolutePath == "/storage/emulated") {
+ parentFolder = getExternalStorageDirectory()
+ }
+ }
+ reload()
+ }
+
+ private fun checkIfCanGoUp() {
+ canGoUp = parentFolder?.parent != null
+ }
+
+ private fun reload() {
+ parentContents = listFiles()
+ val dialog = dialog as MaterialDialog?
+ dialog?.setTitle(parentFolder?.absolutePath)
+ dialog?.updateListItems(items = contentsArray.toCharSequence())
+ }
+
+ private fun Array.toCharSequence(): List {
+ return map { it as CharSequence }
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ outState.putString("current_path", parentFolder?.absolutePath)
+ }
+
+ fun setCallback(callback: FolderCallback?) {
+ this.callback = callback
+ }
+
+ interface FolderCallback {
+ fun onFolderSelection(context: Context, folder: File)
+ }
+
+ companion object {
+ fun create(): BlacklistFolderChooserDialog {
+ return BlacklistFolderChooserDialog()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/ClearSmartPlaylistDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/ClearSmartPlaylistDialog.java
deleted file mode 100644
index 2358c2aad..000000000
--- 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.support.annotation.NonNull;
-import android.support.v4.app.DialogFragment;
-import android.text.Html;
-
-import com.afollestad.materialdialogs.DialogAction;
-import com.afollestad.materialdialogs.MaterialDialog;
-
-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/CreatePlaylistDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.java
deleted file mode 100644
index a09cf4cfa..000000000
--- a/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.java
+++ /dev/null
@@ -1,103 +0,0 @@
-package code.name.monkey.retromusic.dialogs;
-
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.design.widget.BottomSheetDialogFragment;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.EditText;
-
-import java.util.ArrayList;
-import java.util.Objects;
-
-import butterknife.BindView;
-import butterknife.ButterKnife;
-import butterknife.OnClick;
-import code.name.monkey.appthemehelper.ThemeStore;
-import code.name.monkey.appthemehelper.util.TintHelper;
-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)
- EditText playlistName;
- @BindView(R.id.action_cancel)
- Button actionCancel;
- @BindView(R.id.action_create)
- Button actionCreate;
-
- @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()));
- TintHelper.setTintAuto(playlistName, accentColor, true);
- TintHelper.setTintAuto(actionCreate, accentColor, true);
- actionCancel.setTextColor(accentColor);
- }
-
- @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 000000000..7d7a01c90
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.dialogs
+
+import android.app.Dialog
+import android.os.Bundle
+import android.text.TextUtils
+import androidx.core.os.bundleOf
+import androidx.fragment.app.DialogFragment
+import code.name.monkey.retromusic.EXTRA_SONG
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.databinding.DialogPlaylistBinding
+import code.name.monkey.retromusic.extensions.colorButtons
+import code.name.monkey.retromusic.extensions.extra
+import code.name.monkey.retromusic.extensions.materialDialog
+import code.name.monkey.retromusic.fragments.LibraryViewModel
+import code.name.monkey.retromusic.model.Song
+import com.google.android.material.textfield.TextInputEditText
+import com.google.android.material.textfield.TextInputLayout
+import org.koin.androidx.viewmodel.ext.android.activityViewModel
+
+class CreatePlaylistDialog : DialogFragment() {
+ private var _binding: DialogPlaylistBinding? = null
+ private val binding get() = _binding!!
+ private val libraryViewModel by activityViewModel()
+
+ companion object {
+ fun create(song: Song): CreatePlaylistDialog {
+ val list = mutableListOf()
+ list.add(song)
+ return create(list)
+ }
+
+ fun create(songs: List): CreatePlaylistDialog {
+ return CreatePlaylistDialog().apply {
+ arguments = bundleOf(EXTRA_SONG to songs)
+ }
+ }
+ }
+
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ _binding = DialogPlaylistBinding.inflate(layoutInflater)
+
+ val songs: List = extra>(EXTRA_SONG).value ?: emptyList()
+ val playlistView: TextInputEditText = binding.actionNewPlaylist
+ val playlistContainer: TextInputLayout = binding.actionNewPlaylistContainer
+ return materialDialog(R.string.new_playlist_title)
+ .setView(binding.root)
+ .setPositiveButton(
+ R.string.create_action
+ ) { _, _ ->
+ val playlistName = playlistView.text.toString()
+ if (!TextUtils.isEmpty(playlistName)) {
+ libraryViewModel.addToPlaylist(requireContext(), playlistName, songs)
+ } else {
+ playlistContainer.error = "Playlist name can't be empty"
+ }
+ }
+ .setNegativeButton(R.string.action_cancel, null)
+ .create()
+ .colorButtons()
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
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 5e408ca81..000000000
--- a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.java
+++ /dev/null
@@ -1,89 +0,0 @@
-package code.name.monkey.retromusic.dialogs;
-
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.text.Html;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-
-import butterknife.BindView;
-import butterknife.ButterKnife;
-import butterknife.OnClick;
-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)
- TextView delete;
- @BindView(R.id.title)
- TextView title;
- @BindView(R.id.action_cancel)
- TextView cancel;
-
- @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_playlist, container, false);
- ButterKnife.bind(this, layout);
- return layout;
- }
-
- @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) {
- title = R.string.delete_playlists_title;
- content = Html.fromHtml(getString(R.string.delete_x_playlists, playlists.size()));
- } else {
- title = R.string.delete_playlist_title;
- content = Html.fromHtml(getString(R.string.delete_playlist_x, playlists.get(0).name));
- }
- this.title.setText(title);
- this.delete.setText(content);
- }
-
- @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 000000000..30675d88a
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2020 Hemanth Savarla.
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ */
+package code.name.monkey.retromusic.dialogs
+
+import android.app.Dialog
+import android.os.Bundle
+import androidx.core.os.bundleOf
+import androidx.core.text.parseAsHtml
+import androidx.fragment.app.DialogFragment
+import code.name.monkey.retromusic.EXTRA_PLAYLIST
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.db.PlaylistEntity
+import code.name.monkey.retromusic.extensions.colorButtons
+import code.name.monkey.retromusic.extensions.extraNotNull
+import code.name.monkey.retromusic.extensions.materialDialog
+import code.name.monkey.retromusic.fragments.LibraryViewModel
+import code.name.monkey.retromusic.fragments.ReloadType
+import org.koin.androidx.viewmodel.ext.android.activityViewModel
+
+class DeletePlaylistDialog : DialogFragment() {
+
+ private val libraryViewModel by activityViewModel()
+
+ companion object {
+
+ fun create(playlist: PlaylistEntity): DeletePlaylistDialog {
+ val list = mutableListOf()
+ list.add(playlist)
+ return create(list)
+ }
+
+ fun create(playlists: List): DeletePlaylistDialog {
+ return DeletePlaylistDialog().apply {
+ arguments = bundleOf(EXTRA_PLAYLIST to playlists)
+ }
+ }
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val playlists = extraNotNull>(EXTRA_PLAYLIST).value
+ val title: Int
+ val message: CharSequence
+ //noinspection ConstantConditions
+ if (playlists.size > 1) {
+ title = R.string.delete_playlists_title
+ message =
+ String.format(getString(R.string.delete_x_playlists), playlists.size).parseAsHtml()
+ } else {
+ title = R.string.delete_playlist_title
+ message =
+ String.format(getString(R.string.delete_playlist_x), playlists[0].playlistName)
+ .parseAsHtml()
+ }
+
+ return materialDialog(title)
+ .setTitle(title)
+ .setMessage(message)
+ .setNegativeButton(android.R.string.cancel, null)
+ .setPositiveButton(R.string.action_delete) { _, _ ->
+ libraryViewModel.deleteSongsFromPlaylist(playlists)
+ libraryViewModel.deleteRoomPlaylist(playlists)
+ libraryViewModel.forceReload(ReloadType.Playlists)
+ }
+ .create()
+ .colorButtons()
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.java
deleted file mode 100644
index 9a30ba3f7..000000000
--- a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package code.name.monkey.retromusic.dialogs;
-
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-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.retromusic.R;
-import code.name.monkey.retromusic.model.Song;
-import code.name.monkey.retromusic.util.MusicUtil;
-import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment;
-import java.util.ArrayList;
-
-public class DeleteSongsDialog extends RoundedBottomSheetDialogFragment {
-
- @BindView(R.id.action_delete)
- TextView delete;
- @BindView(R.id.title)
- TextView title;
- @BindView(R.id.action_cancel)
- TextView cancel;
-
- @NonNull
- public static DeleteSongsDialog create(Song song) {
- ArrayList list = new ArrayList<>();
- list.add(song);
- return create(list);
- }
-
- @NonNull
- public static DeleteSongsDialog create(ArrayList songs) {
- DeleteSongsDialog dialog = new DeleteSongsDialog();
- Bundle args = new Bundle();
- args.putParcelableArrayList("songs", songs);
- dialog.setArguments(args);
- return dialog;
- }
-
- @OnClick({R.id.action_cancel, R.id.action_delete})
- void actions(View view) {
- //noinspection ConstantConditions
- final ArrayList songs = getArguments().getParcelableArrayList("songs");
- switch (view.getId()) {
- case R.id.action_delete:
- if (getActivity() == null) {
- return;
- }
- if (songs != null) {
- MusicUtil.deleteTracks(getActivity(), songs);
- }
- break;
- default:
- }
- dismiss();
- }
-
- @Override
- public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- //noinspection unchecked,ConstantConditions
- final ArrayList