(...);
+}
+-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
+ **[] $VALUES;
+ public *;
+}
+-keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder {
+ *** rewind();
}
--keep class !android.support.v7.internal.view.menu.**,** {*;}
+# OkHttp
+-keepattributes Signature
+-keepattributes *Annotation*
+-keep interface com.squareup.okhttp3.** { *; }
+-dontwarn com.squareup.okhttp3.**
--dontwarn
--ignorewarnings
+#-dontwarn
+#-ignorewarnings
--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 { *; }
+#Jaudiotagger
+-dontwarn org.jaudiotagger.**
+-dontwarn org.jcodec.**
+-keep class org.jaudiotagger.** { *; }
+-keep class org.jcodec.** { *; }
-#-dontwarn android.support.v8.renderscript.*
-#-keepclassmembers class android.support.v8.renderscript.RenderScript {
-# native *** rsn*(...);
-# native *** n*(...);
-#}
-
-#-keep class org.jaudiotagger.** { *; }
-
-
--obfuscationdictionary build/obfuscation-dictionary.txt
--classobfuscationdictionary build/class-dictionary.txt
--packageobfuscationdictionary build/package-dictionary.txt
+-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/release/output-metadata.json b/app/release/output-metadata.json
deleted file mode 100644
index 08c9ac789..000000000
--- a/app/release/output-metadata.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "version": 1,
- "artifactType": {
- "type": "APK",
- "kind": "Directory"
- },
- "applicationId": "io.github.muntashirakon.Music",
- "variantName": "release",
- "elements": [
- {
- "type": "SINGLE",
- "filters": [],
- "properties": [],
- "versionCode": 10443,
- "versionName": "3.5.10",
- "enabled": true,
- "outputFile": "app-release.apk"
- }
- ]
-}
\ No newline at end of file
diff --git a/app/release/output.json b/app/release/output.json
deleted file mode 100644
index 758f3ea4a..000000000
--- a/app/release/output.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "version": 1,
- "artifactType": {
- "type": "APK",
- "kind": "Directory"
- },
- "applicationId": "code.name.monkey.retromusic",
- "variantName": "release",
- "elements": [
- {
- "type": "SINGLE",
- "filters": [],
- "properties": [],
- "versionCode": 10438,
- "versionName": "10438",
- "enabled": true,
- "outputFile": "app-release.apk"
- }
- ]
-}
\ 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 30cf2312c..14fbaaab7 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,39 +1,57 @@
-
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+ android:usesCleartextTraffic="true"
+ tools:ignore="UnusedAttribute">
+ android:exported="true"
+ android:launchMode="singleTop"
+ android:theme="@style/Theme.RetroMusic.SplashScreen">
@@ -44,7 +62,6 @@
-
@@ -104,30 +121,54 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
-
+
-
+
@@ -174,7 +216,7 @@
@@ -187,7 +229,7 @@
@@ -199,7 +241,7 @@
@@ -211,7 +253,7 @@
@@ -223,7 +265,7 @@
@@ -233,13 +275,36 @@
android:name="android.appwidget.provider"
android:resource="@xml/app_widget_card_info" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ android:foregroundServiceType="mediaPlayback"
+ android:label="@string/app_name">
@@ -253,15 +318,33 @@
android:name="com.lge.support.SPLIT_WINDOW"
android:value="true" />
+
+ android:name="com.google.android.gms.car.application"
+ android:resource="@xml/automotive_app_desc" />
+
+
-
-
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/assets/contributors.json b/app/src/main/assets/contributors.json
deleted file mode 100644
index 2efd24509..000000000
--- a/app/src/main/assets/contributors.json
+++ /dev/null
@@ -1,26 +0,0 @@
-[
- {
- "name": "Hemanth Savarala",
- "summary": "Lead Developer & Designer",
- "link": "https://github.com/h4h13",
- "profile_image": "https://i.imgur.com/AoVs9oj.jpg"
- },
- {
- "name": "Lennart Glamann",
- "summary": "Play Store banner and Images",
- "link": "https://t.me/FlixbusLennart",
- "profile_image": "https://i.imgur.com/Q5Nsx1R.jpg"
- },
- {
- "name": "Daksh P. Jain",
- "summary": "Telegram group maintainer",
- "link": "https://dakshpjain.eu.org",
- "profile_image": "https://i.imgur.com/fnYpg65.jpg"
- },
- {
- "name": "Milind Goel",
- "summary": "Github & Telegram maintainer",
- "link": "https://t.me/MilindGoel15",
- "profile_image": "https://i.imgur.com/Bz4De21_d.jpg"
- }
-]
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/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/oldindex.html b/app/src/main/assets/oldindex.html
deleted file mode 100644
index 367da632a..000000000
--- a/app/src/main/assets/oldindex.html
+++ /dev/null
@@ -1,63 +0,0 @@
-
-
-
-
-
-
-
-
-
- Phonograph by
- Karim Abou Zeid
- Material Dialogs and Cab
- by Aidan Michael Follestad
- AOSP Support Libraries by AOSP contributors
- Glide by Sam Judd
- Retrofit by Square team
-
-
- Material Contextual Action Bar by Aidan Michael Follestad
- OkHttp by Square team
-
- CircleImageView 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
- 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
index aa3c01e7f..b6b1c411a 100644
--- a/app/src/main/assets/retro-changelog.html
+++ b/app/src/main/assets/retro-changelog.html
@@ -6,22 +6,23 @@
word-wrap: break-word;
}
- body {
- padding-left: 1rem;
- padding-right: 1rem;
+ div {
+ margin: 20px 10px;
+ padding: 10px;
+ border-radius: 10px;
+ box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
}
h2 {
margin-block-end: 0rem;
margin-block-start: 0rem;
+ display: inline-block;
+ vertical-align: center;
}
li {
font-size: 0.85rem;
- padding-top: 0.5rem;
- padding-left: 0;
- padding-right: 0;
- color: rgba(0, 0, 0, 0.8);
+ padding: 0.5rem 0;
}
ul {
@@ -36,7 +37,6 @@
span {
font-size: 0.7rem;
- line-height: 0.7rem;
}
h5 {
@@ -44,39 +44,423 @@
margin-block-end: 0.5rem;
}
- h3 span {
- border-radius: 0.2rem;
- padding-left: 0.5rem;
- padding-right: 0.5rem;
- padding-top: 0.3rem;
- padding-bottom: 0.3rem;
+ h3 {
+ margin: 10px 0px;
font-size: 1rem;
}
+
+ .tag {
+ border-radius: 5px;
+ margin-left: 10px;
+ padding: 5px;
+ display: inline-block;
+ vertical-align: top;
+ }
+
{style-placeholder}
-
-
-April 30, 2020
-v3.5.110
-Beta version
-
-
- Changed profile form image to icon
- New what's new screen
- Added In-App language changer, where you can select language
-
-
-
- Improved loading of Songs, Albums, Artists, Genres, Playlists
-
-
-*If you face any UI related issues you clear app data and cache, if itsnot working try to
- uninstall and install
- again.
+
+
+
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
index 991844b5e..aa6e4ae9a 100644
Binary files a/app/src/main/ic_launcher-playstore.png 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 ff0f8b9ff..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/io/github/muntashirakon/music/Constants.kt b/app/src/main/java/code/name/monkey/retromusic/Constants.kt
similarity index 62%
rename from app/src/main/java/io/github/muntashirakon/music/Constants.kt
rename to app/src/main/java/code/name/monkey/retromusic/Constants.kt
index fd01d4335..31032f62b 100644
--- a/app/src/main/java/io/github/muntashirakon/music/Constants.kt
+++ b/app/src/main/java/code/name/monkey/retromusic/Constants.kt
@@ -1,59 +1,62 @@
/*
- * Copyright (c) 2019 Hemanth Savarala.
+ * 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 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 io.github.muntashirakon.music
+package code.name.monkey.retromusic
import android.provider.BaseColumns
import android.provider.MediaStore
object Constants {
- const val RATE_ON_GOOGLE_PLAY =
- "https://play.google.com/store/apps/details?id=code.name.monkey.retromusic"
- const val TRANSLATE = "https://github.com/h4h13/RetroMusicPlayer"
+ 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/retromusiclog"
+ const val TELEGRAM_CHANGE_LOG = "https://t.me/AppManagerChannel"
const val USER_PROFILE = "profile.jpg"
const val USER_BANNER = "banner.jpg"
- const val APP_INSTAGRAM_LINK = "https://www.instagram.com/retromusicapp/"
- const val APP_TELEGRAM_LINK = "https://t.me/retromusicapp/"
- const val APP_TWITTER_LINK = "https://twitter.com/retromusicapp"
- const val FAQ_LINK = "https://github.com/h4h13/RetroMusicPlayer/blob/master/FAQ.md"
- const val PINTEREST = "https://in.pinterest.com/retromusicapp/"
- const val BASE_URL = "https://ws.audioscrobbler.com/2.0/"
+ 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
- MediaStore.Audio.AudioColumns.DATA, // 5
+ 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
+ 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_PLAYLIST = "extra_list"
+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"
@@ -64,29 +67,27 @@ 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 GAPLESS_PLAYBACK = "gapless_playback"
-const val ALBUM_ART_ON_LOCKSCREEN = "album_art_on_lockscreen"
+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 ROUND_CORNERS = "corner_window"
-const val TOGGLE_GENRE = "toggle_genre"
-const val PROFILE_IMAGE_PATH = "profile_image_path"
-const val BANNER_IMAGE_PATH = "banner_image_path"
const val ADAPTIVE_COLOR_APP = "adaptive_color_app"
-const val TOGGLE_SEPARATE_LINE = "toggle_separate_line"
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 DIALOG_CORNER = "dialog_corner"
+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"
@@ -94,12 +95,12 @@ 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 LAST_PAGE = "last_start_page"
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"
@@ -107,22 +108,47 @@ 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 AUDIO_DUCKING = "audio_ducking"
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 AUTO_DOWNLOAD_IMAGES_POLICY = "auto_download_images_policy"
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 TOGGLE_SHUFFLE = "toggle_shuffle"
+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/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/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/io/github/muntashirakon/music/activities/base/AbsMusicServiceActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsMusicServiceActivity.kt
similarity index 54%
rename from app/src/main/java/io/github/muntashirakon/music/activities/base/AbsMusicServiceActivity.kt
rename to app/src/main/java/code/name/monkey/retromusic/activities/base/AbsMusicServiceActivity.kt
index c7da95d2a..f9f05c919 100644
--- a/app/src/main/java/io/github/muntashirakon/music/activities/base/AbsMusicServiceActivity.kt
+++ b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsMusicServiceActivity.kt
@@ -1,20 +1,49 @@
-package io.github.muntashirakon.music.activities.base
+/*
+ * 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 io.github.muntashirakon.music.R
-import io.github.muntashirakon.music.helper.MusicPlayerRemote
-import io.github.muntashirakon.music.interfaces.MusicServiceEventListener
-import io.github.muntashirakon.music.service.MusicService.*
+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
-import java.util.*
-abstract class AbsMusicServiceActivity : AbsBaseActivity(), MusicServiceEventListener {
-
- private val mMusicServiceEventListeners = ArrayList()
+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
@@ -43,15 +72,15 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), MusicServiceEventLis
}
}
- fun addMusicServiceEventListener(listener: MusicServiceEventListener?) {
- if (listener != null) {
- mMusicServiceEventListeners.add(listener)
+ fun addMusicServiceEventListener(listenerI: IMusicServiceEventListener?) {
+ if (listenerI != null) {
+ mMusicServiceEventListeners.add(listenerI)
}
}
- fun removeMusicServiceEventListener(listener: MusicServiceEventListener?) {
- if (listener != null) {
- mMusicServiceEventListeners.remove(listener)
+ fun removeMusicServiceEventListener(listenerI: IMusicServiceEventListener?) {
+ if (listenerI != null) {
+ mMusicServiceEventListeners.remove(listenerI)
}
}
@@ -68,8 +97,7 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), MusicServiceEventLis
filter.addAction(MEDIA_STORE_CHANGED)
filter.addAction(FAVORITE_STATE_CHANGED)
- registerReceiver(musicStateReceiver, filter)
-
+ ContextCompat.registerReceiver(this, musicStateReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED)
receiverRegistered = true
}
@@ -93,6 +121,16 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), MusicServiceEventLis
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() {
@@ -125,6 +163,12 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), MusicServiceEventLis
}
}
+ override fun onFavoriteStateChanged() {
+ for (listener in mMusicServiceEventListeners) {
+ listener.onFavoriteStateChanged()
+ }
+ }
+
override fun onHasPermissionsChanged(hasPermissions: Boolean) {
super.onHasPermissionsChanged(hasPermissions)
val intent = Intent(MEDIA_STORE_CHANGED)
@@ -133,15 +177,21 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), MusicServiceEventLis
true
) // just in case we need to know this at some point
sendBroadcast(intent)
- println("sendBroadcast $hasPermissions")
+ logD("sendBroadcast $hasPermissions")
}
override fun getPermissionsToRequest(): Array {
- return arrayOf(
- Manifest.permission.READ_EXTERNAL_STORAGE,
- Manifest.permission.WRITE_EXTERNAL_STORAGE,
- Manifest.permission.BLUETOOTH
- )
+ 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() {
@@ -153,7 +203,8 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), MusicServiceEventLis
val activity = reference.get()
if (activity != null && action != null) {
when (action) {
- FAVORITE_STATE_CHANGED, META_CHANGED -> activity.onPlayingMetaChanged()
+ FAVORITE_STATE_CHANGED -> activity.onFavoriteStateChanged()
+ META_CHANGED -> activity.onPlayingMetaChanged()
QUEUE_CHANGED -> activity.onQueueChanged()
PLAY_STATE_CHANGED -> activity.onPlayStateChanged()
REPEAT_MODE_CHANGED -> activity.onRepeatModeChanged()
diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt
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/io/github/muntashirakon/music/activities/tageditor/AbsTagEditorActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AbsTagEditorActivity.kt
similarity index 53%
rename from app/src/main/java/io/github/muntashirakon/music/activities/tageditor/AbsTagEditorActivity.kt
rename to app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AbsTagEditorActivity.kt
index 19990376c..3a9ca4e4b 100755
--- a/app/src/main/java/io/github/muntashirakon/music/activities/tageditor/AbsTagEditorActivity.kt
+++ b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AbsTagEditorActivity.kt
@@ -1,52 +1,83 @@
-package io.github.muntashirakon.music.activities.tageditor
+/*
+ * 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.content.res.ColorStateList
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
-import android.os.Build
import android.os.Bundle
+import android.provider.MediaStore
import android.util.Log
+import android.view.LayoutInflater
import android.view.MenuItem
-import android.view.View
import android.view.animation.OvershootInterpolator
+import android.widget.ImageView
+import androidx.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 code.name.monkey.appthemehelper.ThemeStore
-import code.name.monkey.appthemehelper.util.ATHUtil
-import code.name.monkey.appthemehelper.util.ColorUtil
-import code.name.monkey.appthemehelper.util.MaterialValueHelper
-import code.name.monkey.appthemehelper.util.TintHelper
-import io.github.muntashirakon.music.R
-import io.github.muntashirakon.music.R.drawable
-import io.github.muntashirakon.music.activities.base.AbsBaseActivity
-import io.github.muntashirakon.music.activities.saf.SAFGuideActivity
-import io.github.muntashirakon.music.util.RetroUtil
-import io.github.muntashirakon.music.util.SAFUtil
+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.android.synthetic.main.activity_album_tag_editor.*
+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
-import java.util.*
-abstract class AbsTagEditorActivity : AbsBaseActivity() {
+abstract class AbsTagEditorActivity : AbsBaseActivity() {
+ abstract val editorImage: ImageView
+ val repository by inject()
- protected var id: Int = 0
+ lateinit var saveFab: MaterialButton
+ protected var id: Long = 0
private set
private var paletteColorPrimary: Int = 0
- private var isInNoImageMode: Boolean = false
private var songPaths: List? = null
- lateinit var saveFab: MaterialButton
-
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() =
@@ -59,14 +90,16 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
2 -> deleteImage()
}
}
+ .setNegativeButton(R.string.action_cancel, null)
.show()
- protected abstract val contentViewLayout: Int
+ .colorButtons()
internal val albumArtist: String?
get() {
return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.ALBUM_ARTIST)
- } catch (ignored: Exception) {
+ } catch (e: Exception) {
+ logE(e)
null
}
}
@@ -75,7 +108,8 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
get() {
return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.TITLE)
- } catch (ignored: Exception) {
+ } catch (e: Exception) {
+ logE(e)
null
}
}
@@ -83,7 +117,8 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
get() {
return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.COMPOSER)
- } catch (ignored: Exception) {
+ } catch (e: Exception) {
+ logE(e)
null
}
}
@@ -92,7 +127,8 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
get() {
return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.ALBUM)
- } catch (ignored: Exception) {
+ } catch (e: Exception) {
+ logE(e)
null
}
}
@@ -101,7 +137,8 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
get() {
return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.ARTIST)
- } catch (ignored: Exception) {
+ } catch (e: Exception) {
+ logE(e)
null
}
}
@@ -110,7 +147,8 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
get() {
return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.ALBUM_ARTIST)
- } catch (ignored: Exception) {
+ } catch (e: Exception) {
+ logE(e)
null
}
}
@@ -119,7 +157,8 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
get() {
return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.GENRE)
- } catch (ignored: Exception) {
+ } catch (e: Exception) {
+ logE(e)
null
}
}
@@ -128,7 +167,8 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
get() {
return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.YEAR)
- } catch (ignored: Exception) {
+ } catch (e: Exception) {
+ logE(e)
null
}
}
@@ -137,7 +177,18 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
get() {
return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.TRACK)
- } catch (ignored: Exception) {
+ } 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
}
}
@@ -146,7 +197,8 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
get() {
return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.LYRICS)
- } catch (ignored: Exception) {
+ } catch (e: Exception) {
+ logE(e)
null
}
}
@@ -164,41 +216,44 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
)
}
return null
- } catch (ignored: Exception) {
+ } catch (e: Exception) {
+ logE(e)
return null
}
}
+ private val pickArtworkImage =
+ registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
+ loadImageFromFile(uri)
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- setContentView(contentViewLayout)
+ _binding = bindingInflater.invoke(layoutInflater)
+ setContentView(binding.root)
+ setTaskDescriptionColorAuto()
saveFab = findViewById(R.id.saveTags)
getIntentExtras()
songPaths = getSongPaths()
+ logD(songPaths?.size)
if (songPaths!!.isEmpty()) {
finish()
- return
}
-
setUpViews()
-
- setStatusbarColorAuto()
- setNavigationbarColorAuto()
- setTaskDescriptionColorAuto()
+ launcher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
+ if (it.resultCode == Activity.RESULT_OK) {
+ writeToFiles(getSongUris(), cacheFiles)
+ }
+ }
}
private fun setUpViews() {
- setUpScrollView()
setUpFab()
setUpImageView()
}
- private fun setUpScrollView() {
- //observableScrollView.setScrollViewCallbacks(observableScrollViewCallbacks);
- }
-
private lateinit var items: List
private fun setUpImageView() {
@@ -208,18 +263,11 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
getString(R.string.web_search),
getString(R.string.remove_cover)
)
- editorImage?.setOnClickListener { show }
+ editorImage.setOnClickListener { show }
}
private fun startImagePicker() {
- val intent = Intent(Intent.ACTION_GET_CONTENT)
- intent.type = "image/*"
- startActivityForResult(
- Intent.createChooser(
- intent,
- getString(R.string.pick_from_local_storage)
- ), REQUEST_CODE_SELECT_IMAGE
- )
+ pickArtworkImage.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
}
protected abstract fun loadCurrentImage()
@@ -229,26 +277,12 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
protected abstract fun deleteImage()
private fun setUpFab() {
- saveFab.backgroundTintList = ColorStateList.valueOf(ThemeStore.accentColor(this))
- ColorStateList.valueOf(
- MaterialValueHelper.getPrimaryTextColor(
- this,
- ColorUtil.isColorLight(
- ThemeStore.accentColor(
- this
- )
- )
- )
- ).apply {
- saveFab.setTextColor(this)
- saveFab.iconTint = this
- }
+ saveFab.accentColor()
saveFab.apply {
scaleX = 0f
scaleY = 0f
isEnabled = false
setOnClickListener { save() }
- TintHelper.setTintAuto(this, ThemeStore.accentColor(this@AbsTagEditorActivity), true)
}
}
@@ -257,12 +291,14 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
private fun getIntentExtras() {
val intentExtras = intent.extras
if (intentExtras != null) {
- id = intentExtras.getInt(EXTRA_ID)
+ 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) {
@@ -279,27 +315,13 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
- super.onBackPressed()
+ onBackPressedDispatcher.onBackPressed()
return true
}
}
return super.onOptionsItemSelected(item)
}
- protected fun setNoImageMode() {
- isInNoImageMode = true
- imageContainer?.visibility = View.GONE
- editorImage?.visibility = View.GONE
- editorImage?.isEnabled = false
-
- setColors(
- intent.getIntExtra(
- EXTRA_PALETTE,
- ATHUtil.resolveColor(this, R.attr.colorPrimary)
- )
- )
- }
-
protected fun dataChanged() {
showFab()
}
@@ -318,7 +340,7 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
protected fun setImageBitmap(bitmap: Bitmap?, bgColor: Int) {
if (bitmap == null) {
- editorImage.setImageResource(drawable.default_audio_art)
+ editorImage.setImageResource(R.drawable.default_audio_art)
} else {
editorImage.setImageBitmap(bitmap)
}
@@ -330,86 +352,106 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
}
protected fun writeValuesToFiles(
- fieldKeyValueMap: Map, artworkInfo: ArtworkInfo?
+ fieldKeyValueMap: Map,
+ artworkInfo: ArtworkInfo?
) {
- RetroUtil.hideSoftKeyboard(this)
+ hideSoftKeyboard()
hideFab()
-
- savedSongPaths = getSongPaths()
- savedTags = fieldKeyValueMap
- savedArtworkInfo = artworkInfo
-
- if (!SAFUtil.isSAFRequired(savedSongPaths)) {
- writeTags(savedSongPaths)
- } else {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- if (SAFUtil.isSDCardAccessGranted(this)) {
- writeTags(savedSongPaths)
- } else {
- startActivityForResult(
- Intent(this, SAFGuideActivity::class.java),
- SAFGuideActivity.REQUEST_CODE_SAF_GUIDE
+ 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?) {
- WriteTagsAsyncTask(this).execute(
- WriteTagsAsyncTask.LoadingInfo(
- paths,
- savedTags,
- savedArtworkInfo
- )
- )
- }
+ GlobalScope.launch {
+ if (VersionUtils.hasR()) {
+ cacheFiles = TagWriter.writeTagsToFilesR(
+ this@AbsTagEditorActivity, AudioTagInfo(
+ paths,
+ savedTags,
+ savedArtworkInfo
+ )
+ )
+ val pendingIntent = MediaStore.createWriteRequest(contentResolver, getSongUris())
- override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
- super.onActivityResult(requestCode, resultCode, intent)
- when (requestCode) {
- REQUEST_CODE_SELECT_IMAGE -> if (resultCode == Activity.RESULT_OK) {
- intent?.data?.let {
- loadImageFromFile(it)
- }
- }
- SAFGuideActivity.REQUEST_CODE_SAF_GUIDE -> {
- SAFUtil.openTreePicker(this)
- }
- SAFUtil.REQUEST_SAF_PICK_TREE -> {
- if (resultCode == Activity.RESULT_OK) {
- SAFUtil.saveTreeUri(this, intent)
- writeTags(savedSongPaths)
- }
- }
- SAFUtil.REQUEST_SAF_PICK_FILE -> {
- if (resultCode == Activity.RESULT_OK) {
- writeTags(Collections.singletonList(currentSongPath + SAFUtil.SEPARATOR + intent!!.dataString))
- }
+ launcher.launch(IntentSenderRequest.Builder(pendingIntent).build())
+ } else {
+ TagWriter.writeTagsToFiles(
+ this@AbsTagEditorActivity, AudioTagInfo(
+ paths,
+ savedTags,
+ savedArtworkInfo
+ )
+ )
}
}
}
- protected abstract fun loadImageFromFile(selectedFile: Uri?)
+ private lateinit var audioFile: AudioFile
private fun getAudioFile(path: String): AudioFile {
return try {
- AudioFileIO.read(File(path))
+ if (!this::audioFile.isInitialized) {
+ audioFile = AudioFileIO.read(File(path))
+ }
+ audioFile
} catch (e: Exception) {
Log.e(TAG, "Could not read audio file $path", e)
AudioFile()
}
}
- class ArtworkInfo constructor(val albumId: Int, val artwork: Bitmap?)
+ 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/io/github/muntashirakon/music/adapter/SongFileAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/SongFileAdapter.kt
similarity index 65%
rename from app/src/main/java/io/github/muntashirakon/music/adapter/SongFileAdapter.kt
rename to app/src/main/java/code/name/monkey/retromusic/adapter/SongFileAdapter.kt
index a7fd9e87c..4005c6e6c 100644
--- a/app/src/main/java/io/github/muntashirakon/music/adapter/SongFileAdapter.kt
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/SongFileAdapter.kt
@@ -1,19 +1,18 @@
/*
- * Copyright 2019 Google LLC
+ * Copyright (c) 2020 Hemanth Savarla.
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the GNU General Public License v3
*
- * https://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
*/
-package io.github.muntashirakon.music.adapter
+package code.name.monkey.retromusic.adapter
import android.graphics.PorterDuff
import android.view.LayoutInflater
@@ -21,32 +20,31 @@ 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 io.github.muntashirakon.music.R
-import io.github.muntashirakon.music.adapter.base.AbsMultiSelectAdapter
-import io.github.muntashirakon.music.adapter.base.MediaEntryViewHolder
-import io.github.muntashirakon.music.glide.audiocover.AudioFileCover
-import io.github.muntashirakon.music.interfaces.CabHolder
-import io.github.muntashirakon.music.util.MusicUtil
-import io.github.muntashirakon.music.util.RetroUtil
+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 java.util.*
import kotlin.math.log10
import kotlin.math.pow
class SongFileAdapter(
- private val activity: AppCompatActivity,
+ override val activity: AppCompatActivity,
private var dataSet: List,
private val itemLayoutRes: Int,
- private val callbacks: Callbacks?,
- cabHolder: CabHolder?
+ private val iCallbacks: ICallbacks?
) : AbsMultiSelectAdapter(
- activity, cabHolder, R.menu.menu_media_selection
+ activity, R.menu.menu_media_selection
), PopupTextProvider {
init {
@@ -78,7 +76,7 @@ class SongFileAdapter(
if (holder.itemViewType == FILE) {
holder.text?.text = getFileText(file)
} else {
- holder.text?.visibility = View.GONE
+ holder.text?.isVisible = false
}
}
@@ -96,7 +94,7 @@ class SongFileAdapter(
}
private fun loadFileImage(file: File, holder: ViewHolder) {
- val iconColor = ATHUtil.resolveColor(activity, R.attr.colorControlNormal)
+ val iconColor = ATHUtil.resolveColor(activity, androidx.appcompat.R.attr.colorControlNormal)
if (file.isDirectory) {
holder.image?.let {
it.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN)
@@ -105,21 +103,19 @@ class SongFileAdapter(
holder.imageTextContainer?.setCardBackgroundColor(
ATHUtil.resolveColor(
activity,
- R.attr.colorSurface
+ com.google.android.material.R.attr.colorSurface
)
)
} else {
- val error = RetroUtil.getTintedVectorDrawable(
- activity, R.drawable.ic_file_music, iconColor
- )
+ val error = activity.getTintedDrawable(R.drawable.ic_audio_file, iconColor)
Glide.with(activity)
.load(AudioFileCover(file.path))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.error(error)
.placeholder(error)
- .animate(android.R.anim.fade_in)
+ .transition(RetroGlideExtension.getDefaultTransition())
.signature(MediaStoreSignature("", file.lastModified(), 0))
- .into(holder.image)
+ .into(holder.image!!)
}
}
@@ -127,43 +123,35 @@ class SongFileAdapter(
return dataSet.size
}
- override fun getIdentifier(position: Int): File? {
+ override fun getIdentifier(position: Int): File {
return dataSet[position]
}
- override fun getName(`object`: File): String {
- return getFileTitle(`object`)
+ override fun getName(model: File): String {
+ return getFileTitle(model)
}
- override fun onMultipleItemAction(menuItem: MenuItem, selection: ArrayList) {
- if (callbacks == null) return
- callbacks.onMultipleItemAction(menuItem, selection)
+ override fun onMultipleItemAction(menuItem: MenuItem, selection: List) {
+ if (iCallbacks == null) return
+ iCallbacks.onMultipleItemAction(menuItem, selection as ArrayList)
}
override fun getPopupText(position: Int): String {
- return getSectionName(position)
+ return if (position >= dataSet.lastIndex) "" else getSectionName(position)
}
private fun getSectionName(position: Int): String {
return MusicUtil.getSectionName(dataSet[position].name)
}
- interface Callbacks {
- fun onFileSelected(file: File)
-
- fun onFileMenuClicked(file: File, view: View)
-
- fun onMultipleItemAction(item: MenuItem, files: ArrayList)
- }
-
- inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
+ inner class ViewHolder(itemView: View) : code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder(itemView) {
init {
- if (menu != null && callbacks != null) {
+ if (menu != null && iCallbacks != null) {
menu?.setOnClickListener { v ->
val position = layoutPosition
if (isPositionInRange(position)) {
- callbacks.onFileMenuClicked(dataSet[position], v)
+ iCallbacks.onFileMenuClicked(dataSet[position], v)
}
}
}
@@ -178,7 +166,7 @@ class SongFileAdapter(
if (isInQuickSelectMode) {
toggleChecked(position)
} else {
- callbacks?.onFileSelected(dataSet[position])
+ iCallbacks?.onFileSelected(dataSet[position])
}
}
}
@@ -205,4 +193,4 @@ class SongFileAdapter(
return DecimalFormat("#,##0.##").format(size / 1024.0.pow(digitGroups.toDouble())) + " " + units[digitGroups]
}
}
-}
\ No newline at end of file
+}
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/io/github/muntashirakon/music/adapter/album/AlbumAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumAdapter.kt
similarity index 53%
rename from app/src/main/java/io/github/muntashirakon/music/adapter/album/AlbumAdapter.kt
rename to app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumAdapter.kt
index f1147f71a..160af7e47 100644
--- a/app/src/main/java/io/github/muntashirakon/music/adapter/album/AlbumAdapter.kt
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumAdapter.kt
@@ -1,39 +1,51 @@
-package io.github.muntashirakon.music.adapter.album
+/*
+ * 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.app.ActivityOptions
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.appcompat.app.AppCompatActivity
-import io.github.muntashirakon.music.R
-import io.github.muntashirakon.music.adapter.base.AbsMultiSelectAdapter
-import io.github.muntashirakon.music.adapter.base.MediaEntryViewHolder
-import io.github.muntashirakon.music.glide.AlbumGlideRequest
-import io.github.muntashirakon.music.glide.RetroMusicColoredTarget
-import io.github.muntashirakon.music.helper.MusicPlayerRemote
-import io.github.muntashirakon.music.helper.SortOrder
-import io.github.muntashirakon.music.helper.menu.SongsMenuHelper
-import io.github.muntashirakon.music.interfaces.CabHolder
-import io.github.muntashirakon.music.model.Album
-import io.github.muntashirakon.music.model.Song
-import io.github.muntashirakon.music.util.MusicUtil
-import io.github.muntashirakon.music.util.NavigationUtil
-import io.github.muntashirakon.music.util.PreferenceUtil
-import io.github.muntashirakon.music.util.color.MediaNotificationProcessor
+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(
- protected val activity: AppCompatActivity,
+ override val activity: FragmentActivity,
var dataSet: List,
- protected var itemLayoutRes: Int,
- cabHolder: CabHolder?
+ var itemLayoutRes: Int,
+ val listener: IAlbumClickListener?
) : AbsMultiSelectAdapter(
activity,
- cabHolder,
R.menu.menu_media_selection
), PopupTextProvider {
@@ -47,12 +59,7 @@ open class AlbumAdapter(
}
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, parent, false)
- }
+ val view = LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false)
return createViewHolder(view, viewType)
}
@@ -60,12 +67,18 @@ open class AlbumAdapter(
return ViewHolder(view)
}
- private fun getAlbumTitle(album: Album): String? {
+ private fun getAlbumTitle(album: Album): String {
return album.title
}
protected open fun getAlbumText(album: Album): String? {
- return album.artistName
+ return album.albumArtist.let {
+ if (it.isNullOrEmpty()) {
+ album.artistName
+ } else {
+ it
+ }
+ }
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
@@ -74,14 +87,12 @@ open class AlbumAdapter(
holder.itemView.isActivated = isChecked
holder.title?.text = getAlbumTitle(album)
holder.text?.text = getAlbumText(album)
- holder.playSongs?.setOnClickListener {
- album.songs?.let { songs ->
- MusicPlayerRemote.openQueue(
- songs,
- 0,
- true
- )
- }
+ // 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)
}
@@ -93,19 +104,20 @@ open class AlbumAdapter(
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
}
-
- AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong())
- .checkIgnoreMediaStore(activity)
- .generatePalette(activity)
- .build()
+ 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)
}
@@ -117,27 +129,28 @@ open class AlbumAdapter(
}
override fun getItemId(position: Int): Long {
- return dataSet[position].id.toLong()
+ return dataSet[position].id
}
override fun getIdentifier(position: Int): Album? {
return dataSet[position]
}
- override fun getName(album: Album): String {
- return album.title!!
+ override fun getName(model: Album): String {
+ return model.title
}
override fun onMultipleItemAction(
- menuItem: MenuItem, selection: ArrayList
+ menuItem: MenuItem,
+ selection: List
) {
SongsMenuHelper.handleMenuClick(activity, getSongList(selection), menuItem.itemId)
}
- private fun getSongList(albums: List): ArrayList {
+ private fun getSongList(albums: List): List {
val songs = ArrayList()
for (album in albums) {
- songs.addAll(album.songs!!)
+ songs.addAll(album.songs)
}
return songs
}
@@ -151,20 +164,19 @@ open class AlbumAdapter(
when (PreferenceUtil.albumSortOrder) {
SortOrder.AlbumSortOrder.ALBUM_A_Z, SortOrder.AlbumSortOrder.ALBUM_Z_A -> sectionName =
dataSet[position].title
- SortOrder.AlbumSortOrder.ALBUM_ARTIST -> sectionName = dataSet[position].artistName
+
+ 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 {
- setImageTransitionName(activity.getString(R.string.transition_album_art))
- menu?.visibility = View.GONE
+ menu?.isVisible = false
}
override fun onClick(v: View?) {
@@ -172,22 +184,14 @@ open class AlbumAdapter(
if (isInQuickSelectMode) {
toggleChecked(layoutPosition)
} else {
- val activityOptions = ActivityOptions.makeSceneTransitionAnimation(
- activity,
- imageContainerCard ?: image,
- activity.getString(R.string.transition_album_art)
- )
- NavigationUtil.goToAlbumOptions(
- activity,
- dataSet[layoutPosition].id,
- activityOptions
- )
+ image?.let {
+ listener?.onAlbumClick(dataSet[layoutPosition].id, imageContainer ?: it)
+ }
}
}
override fun onLongClick(v: View?): Boolean {
- toggleChecked(layoutPosition)
- return super.onLongClick(v)
+ return toggleChecked(layoutPosition)
}
}
diff --git a/app/src/main/java/io/github/muntashirakon/music/adapter/album/AlbumCoverPagerAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumCoverPagerAdapter.kt
similarity index 67%
rename from app/src/main/java/io/github/muntashirakon/music/adapter/album/AlbumCoverPagerAdapter.kt
rename to app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumCoverPagerAdapter.kt
index 16a6bcba4..5a0a7d3cd 100644
--- a/app/src/main/java/io/github/muntashirakon/music/adapter/album/AlbumCoverPagerAdapter.kt
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumCoverPagerAdapter.kt
@@ -1,25 +1,45 @@
-package io.github.muntashirakon.music.adapter.album
+/*
+ * 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 io.github.muntashirakon.music.R
-import io.github.muntashirakon.music.fragments.AlbumCoverStyle
-import io.github.muntashirakon.music.fragments.NowPlayingScreen.*
-import io.github.muntashirakon.music.glide.RetroMusicColoredTarget
-import io.github.muntashirakon.music.glide.SongGlideRequest
-import io.github.muntashirakon.music.misc.CustomFragmentStatePagerAdapter
-import io.github.muntashirakon.music.model.Song
-import io.github.muntashirakon.music.util.MusicUtil
-import io.github.muntashirakon.music.util.NavigationUtil
-import io.github.muntashirakon.music.util.PreferenceUtil
-import io.github.muntashirakon.music.util.color.MediaNotificationProcessor
+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
@@ -68,17 +88,17 @@ class AlbumCoverPagerAdapter(
class AlbumCoverFragment : Fragment() {
- private lateinit var albumCover: ImageView
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 = requireArguments().getParcelable(SONG_ARG)!!
+ song = BundleCompat.getParcelable(requireArguments(), SONG_ARG, Song::class.java)!!
}
}
@@ -88,25 +108,26 @@ class AlbumCoverPagerAdapter(
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(getLayoutWithPlayerTheme(), container, false)
- albumCover = view.findViewById(R.id.player_image)
- albumCover.setOnClickListener {
- showLyricsDialog()
+ view.setOnClickListener {
+ if (mainActivity.getBottomSheetBehavior().state == STATE_EXPANDED) {
+ showLyricsDialog()
+ }
}
return view
}
private fun showLyricsDialog() {
lifecycleScope.launch(Dispatchers.IO) {
- val data = MusicUtil.getLyrics(song)
+ val data: String? = MusicUtil.getLyrics(song)
withContext(Dispatchers.Main) {
MaterialAlertDialogBuilder(
requireContext(),
- R.style.ThemeOverlay_MaterialComponents_Dialog_Alert
+ com.google.android.material.R.style.ThemeOverlay_MaterialComponents_Dialog_Alert
).apply {
setTitle(song.title)
- setMessage(data)
+ setMessage(if (data.isNullOrEmpty()) "No lyrics found" else data)
setNegativeButton(R.string.synced_lyrics) { _, _ ->
- NavigationUtil.goToLyrics(requireActivity())
+ goToLyrics(requireActivity())
}
show()
}
@@ -117,6 +138,7 @@ class AlbumCoverPagerAdapter(
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
@@ -126,7 +148,6 @@ class AlbumCoverPagerAdapter(
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.Material -> R.layout.fragment_album_material_cover
AlbumCoverStyle.Full -> R.layout.fragment_album_full_cover
AlbumCoverStyle.FullCard -> R.layout.fragment_album_full_card_cover
}
@@ -137,7 +158,7 @@ class AlbumCoverPagerAdapter(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- loadAlbumCover()
+ loadAlbumCover(albumCover = view.findViewById(R.id.player_image))
}
override fun onDestroyView() {
@@ -145,10 +166,13 @@ class AlbumCoverPagerAdapter(
colorReceiver = null
}
- private fun loadAlbumCover() {
- SongGlideRequest.Builder.from(Glide.with(requireContext()), song)
- .checkIgnoreMediaStore(requireContext())
- .generatePalette(requireContext()).build()
+ 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)
@@ -184,9 +208,7 @@ class AlbumCoverPagerAdapter(
fun newInstance(song: Song): AlbumCoverFragment {
val frag = AlbumCoverFragment()
- val args = Bundle()
- args.putParcelable(SONG_ARG, song)
- frag.arguments = args
+ frag.arguments = bundleOf(SONG_ARG to song)
return frag
}
}
@@ -196,4 +218,3 @@ class AlbumCoverPagerAdapter(
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/io/github/muntashirakon/music/adapter/base/MediaEntryViewHolder.java b/app/src/main/java/code/name/monkey/retromusic/adapter/base/MediaEntryViewHolder.java
similarity index 77%
rename from app/src/main/java/io/github/muntashirakon/music/adapter/base/MediaEntryViewHolder.java
rename to app/src/main/java/code/name/monkey/retromusic/adapter/base/MediaEntryViewHolder.java
index 86d7982bf..d68186c0f 100644
--- a/app/src/main/java/io/github/muntashirakon/music/adapter/base/MediaEntryViewHolder.java
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/base/MediaEntryViewHolder.java
@@ -12,23 +12,22 @@
* See the GNU General Public License for more details.
*/
-package io.github.muntashirakon.music.adapter.base;
+package code.name.monkey.retromusic.adapter.base;
import android.graphics.Color;
import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageButton;
+import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.RecyclerView;
+import androidx.appcompat.widget.AppCompatImageView;
import com.google.android.material.card.MaterialCardView;
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractDraggableSwipeableItemViewHolder;
-import io.github.muntashirakon.music.R;
+import code.name.monkey.retromusic.R;
public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHolder
implements View.OnLongClickListener, View.OnClickListener {
@@ -41,18 +40,13 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
@Nullable
public ImageView image;
- @Nullable
- public ImageView artistImage;
-
- @Nullable
- public ImageView playerImage;
-
- @Nullable
- public ViewGroup imageContainer;
@Nullable
public MaterialCardView imageContainerCard;
+ @Nullable
+ public FrameLayout imageContainer;
+
@Nullable
public TextView imageText;
@@ -63,20 +57,17 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
public View mask;
@Nullable
- public View menu;
+ public AppCompatImageView menu;
@Nullable
public View paletteColorContainer;
- @Nullable
- public ImageButton playSongs;
-
- @Nullable
- public RecyclerView recyclerView;
-
@Nullable
public TextView text;
+ @Nullable
+ public TextView text2;
+
@Nullable
public TextView time;
@@ -87,23 +78,20 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
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);
- artistImage = itemView.findViewById(R.id.artistImage);
- playerImage = itemView.findViewById(R.id.player_image);
time = itemView.findViewById(R.id.time);
imageText = itemView.findViewById(R.id.imageText);
- imageContainer = itemView.findViewById(R.id.imageContainer);
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);
- recyclerView = itemView.findViewById(R.id.recycler_view);
mask = itemView.findViewById(R.id.mask);
- playSongs = itemView.findViewById(R.id.playSongs);
dummyContainer = itemView.findViewById(R.id.dummy_view);
if (imageContainerCard != null) {
@@ -113,6 +101,7 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
itemView.setOnLongClickListener(this);
}
+ @Nullable
@Override
public View getSwipeableContainerView() {
return null;
@@ -120,7 +109,6 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
@Override
public void onClick(View v) {
-
}
@Override
@@ -129,11 +117,12 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
}
public void setImageTransitionName(@NonNull String transitionName) {
- if (imageContainerCard != null) {
- imageContainerCard.setTransitionName(transitionName);
- }
- if (image != null) {
- image.setTransitionName(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/io/github/muntashirakon/music/adapter/song/AbsOffsetSongAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/song/AbsOffsetSongAdapter.kt
similarity index 70%
rename from app/src/main/java/io/github/muntashirakon/music/adapter/song/AbsOffsetSongAdapter.kt
rename to app/src/main/java/code/name/monkey/retromusic/adapter/song/AbsOffsetSongAdapter.kt
index 45193c4ba..2d5fd724f 100644
--- a/app/src/main/java/io/github/muntashirakon/music/adapter/song/AbsOffsetSongAdapter.kt
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/song/AbsOffsetSongAdapter.kt
@@ -1,21 +1,33 @@
-package io.github.muntashirakon.music.adapter.song
+/*
+ * 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.appcompat.app.AppCompatActivity
-import io.github.muntashirakon.music.R
-import io.github.muntashirakon.music.helper.MusicPlayerRemote
-import io.github.muntashirakon.music.interfaces.CabHolder
-import io.github.muntashirakon.music.model.Song
+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: AppCompatActivity,
+ activity: FragmentActivity,
dataSet: MutableList,
- @LayoutRes itemLayoutRes: Int,
- cabHolder: CabHolder?
-) : SongAdapter(activity, dataSet, itemLayoutRes, cabHolder) {
+ @LayoutRes itemLayoutRes: Int
+) : SongAdapter(activity, dataSet, itemLayoutRes) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SongAdapter.ViewHolder {
if (viewType == OFFSET_ITEM) {
@@ -76,4 +88,4 @@ abstract class AbsOffsetSongAdapter(
const val OFFSET_ITEM = 0
const val SONG = 1
}
-}
\ No newline at end of file
+}
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/io/github/muntashirakon/music/adapter/song/PlayingQueueAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/song/PlayingQueueAdapter.kt
similarity index 63%
rename from app/src/main/java/io/github/muntashirakon/music/adapter/song/PlayingQueueAdapter.kt
rename to app/src/main/java/code/name/monkey/retromusic/adapter/song/PlayingQueueAdapter.kt
index b58df715e..2d387be44 100644
--- a/app/src/main/java/io/github/muntashirakon/music/adapter/song/PlayingQueueAdapter.kt
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/song/PlayingQueueAdapter.kt
@@ -1,16 +1,34 @@
-package io.github.muntashirakon.music.adapter.song
+/*
+ * 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.appcompat.app.AppCompatActivity
-import io.github.muntashirakon.music.R
-import io.github.muntashirakon.music.helper.MusicPlayerRemote
-import io.github.muntashirakon.music.helper.MusicPlayerRemote.isPlaying
-import io.github.muntashirakon.music.helper.MusicPlayerRemote.playNextSong
-import io.github.muntashirakon.music.helper.MusicPlayerRemote.removeFromQueue
-import io.github.muntashirakon.music.model.Song
-import io.github.muntashirakon.music.util.MusicUtil
-import io.github.muntashirakon.music.util.ViewUtil
+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
@@ -19,17 +37,15 @@ import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemConstant
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 com.h6ah4i.android.widget.advrecyclerview.swipeable.annotation.SwipeableItemResults
import me.zhanghai.android.fastscroll.PopupTextProvider
class PlayingQueueAdapter(
- activity: AppCompatActivity,
+ activity: FragmentActivity,
dataSet: MutableList,
private var current: Int,
- itemLayoutRes: Int
-) : SongAdapter(
- activity, dataSet, itemLayoutRes, null
-), DraggableItemAdapter,
+ itemLayoutRes: Int,
+) : SongAdapter(activity, dataSet, itemLayoutRes),
+ DraggableItemAdapter,
SwipeableItemAdapter,
PopupTextProvider {
@@ -41,8 +57,8 @@ class PlayingQueueAdapter(
override fun onBindViewHolder(holder: SongAdapter.ViewHolder, position: Int) {
super.onBindViewHolder(holder, position)
- holder.imageText?.text = (position - current).toString()
- holder.time?.text = MusicUtil.getReadableDurationString(dataSet[position].duration)
+ val song = dataSet[position]
+ holder.time?.text = MusicUtil.getReadableDurationString(song.duration)
if (holder.itemViewType == HISTORY || holder.itemViewType == CURRENT) {
setAlpha(holder, 0.5f)
}
@@ -58,7 +74,13 @@ class PlayingQueueAdapter(
}
override fun loadAlbumCover(song: Song, holder: SongAdapter.ViewHolder) {
- // We don't want to load it in this adapter
+ if (holder.image == null) {
+ return
+ }
+ Glide.with(activity)
+ .load(RetroGlideExtension.getSongModel(song))
+ .songCoverOptions(song)
+ .into(holder.image!!)
}
fun swapDataSet(dataSet: List, position: Int) {
@@ -76,7 +98,6 @@ class PlayingQueueAdapter(
holder.image?.alpha = alpha
holder.title?.alpha = alpha
holder.text?.alpha = alpha
- holder.imageText?.alpha = alpha
holder.paletteColorContainer?.alpha = alpha
holder.dragView?.alpha = alpha
holder.menu?.alpha = alpha
@@ -129,8 +150,15 @@ class PlayingQueueAdapter(
}
init {
- imageText?.visibility = View.VISIBLE
- dragView?.visibility = View.VISIBLE
+ dragView?.isVisible = true
+ }
+
+ override fun onClick(v: View?) {
+ if (isInQuickSelectMode) {
+ toggleChecked(layoutPosition)
+ } else {
+ MusicPlayerRemote.playSongAt(layoutPosition)
+ }
}
override fun onSongMenuItemClick(item: MenuItem): Boolean {
@@ -152,8 +180,8 @@ class PlayingQueueAdapter(
mDragStateFlags = flags
}
- override fun getSwipeableContainerView(): View? {
- return dummyContainer
+ override fun getSwipeableContainerView(): View {
+ return dummyContainer!!
}
}
@@ -164,52 +192,46 @@ class PlayingQueueAdapter(
private const val UP_NEXT = 2
}
- override fun onSwipeItem(
- holder: ViewHolder?,
- position: Int, @SwipeableItemResults result: Int
- ): SwipeResultAction {
- return if (result === SwipeableItemConstants.RESULT_CANCELED) {
+ override fun onSwipeItem(holder: ViewHolder, position: Int, result: Int): SwipeResultAction {
+ return if (result == SwipeableItemConstants.RESULT_CANCELED) {
SwipeResultActionDefault()
} else {
- SwipedResultActionRemoveItem(this, position, activity)
+ SwipedResultActionRemoveItem(this, position)
}
}
- override fun onGetSwipeReactionType(holder: ViewHolder?, position: Int, x: Int, y: Int): Int {
- return if (onCheckCanStartDrag(holder!!, position, x, y)) {
+ 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(p0: ViewHolder?, p1: Int) {
+ override fun onSwipeItemStarted(holder: ViewHolder, p1: Int) {
}
- override fun onSetSwipeBackground(holder: ViewHolder?, position: Int, result: Int) {
+ override fun onSetSwipeBackground(holder: ViewHolder, position: Int, result: Int) {
}
internal class SwipedResultActionRemoveItem(
private val adapter: PlayingQueueAdapter,
private val position: Int,
- private val activity: AppCompatActivity
) : SwipeResultActionRemoveItem() {
private var songToRemove: Song? = null
- private val isPlaying: Boolean = MusicPlayerRemote.isPlaying
- private val songProgressMillis = 0
override fun onPerformAction() {
- //currentlyShownSnackbar = null
+ // currentlyShownSnackbar = null
}
override fun onSlideAnimationEnd() {
- //initializeSnackBar(adapter, position, activity, isPlaying)
+ // initializeSnackBar(adapter, position, activity, isPlaying)
songToRemove = adapter.dataSet[position]
- //If song removed was the playing song, then play the next song
+ // 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
+ // 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/io/github/muntashirakon/music/adapter/song/SongAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/song/SongAdapter.kt
similarity index 60%
rename from app/src/main/java/io/github/muntashirakon/music/adapter/song/SongAdapter.kt
rename to app/src/main/java/code/name/monkey/retromusic/adapter/song/SongAdapter.kt
index 42d260b4b..7b7b7bf99 100644
--- a/app/src/main/java/io/github/muntashirakon/music/adapter/song/SongAdapter.kt
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/song/SongAdapter.kt
@@ -1,31 +1,47 @@
-package io.github.muntashirakon.music.adapter.song
+/*
+ * 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.app.ActivityOptions
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.appcompat.app.AppCompatActivity
-import io.github.muntashirakon.music.R
-import io.github.muntashirakon.music.adapter.base.AbsMultiSelectAdapter
-import io.github.muntashirakon.music.adapter.base.MediaEntryViewHolder
-import io.github.muntashirakon.music.extensions.hide
-import io.github.muntashirakon.music.extensions.show
-import io.github.muntashirakon.music.glide.RetroMusicColoredTarget
-import io.github.muntashirakon.music.glide.SongGlideRequest
-import io.github.muntashirakon.music.helper.MusicPlayerRemote
-import io.github.muntashirakon.music.helper.SortOrder
-import io.github.muntashirakon.music.helper.menu.SongMenuHelper
-import io.github.muntashirakon.music.helper.menu.SongsMenuHelper
-import io.github.muntashirakon.music.interfaces.CabHolder
-import io.github.muntashirakon.music.model.Song
-import io.github.muntashirakon.music.util.MusicUtil
-import io.github.muntashirakon.music.util.NavigationUtil
-import io.github.muntashirakon.music.util.PreferenceUtil
-import io.github.muntashirakon.music.util.color.MediaNotificationProcessor
-import com.afollestad.materialcab.MaterialCab
+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
@@ -34,16 +50,14 @@ import me.zhanghai.android.fastscroll.PopupTextProvider
*/
open class SongAdapter(
- protected val activity: AppCompatActivity,
+ override val activity: FragmentActivity,
var dataSet: MutableList,
protected var itemLayoutRes: Int,
- cabHolder: CabHolder?,
showSectionName: Boolean = true
) : AbsMultiSelectAdapter(
activity,
- cabHolder,
R.menu.menu_media_selection
-), MaterialCab.Callback, PopupTextProvider {
+), PopupTextProvider {
private var showSectionName = true
@@ -58,7 +72,7 @@ open class SongAdapter(
}
override fun getItemId(position: Int): Long {
- return dataSet[position].id.toLong()
+ return dataSet[position].id
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
@@ -79,14 +93,15 @@ open class SongAdapter(
val song = dataSet[position]
val isChecked = isChecked(song)
holder.itemView.isActivated = isChecked
- if (isChecked) {
- holder.menu?.hide()
- } else {
- holder.menu?.show()
- }
+ 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) {
@@ -94,6 +109,7 @@ open class SongAdapter(
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)
}
@@ -102,9 +118,10 @@ open class SongAdapter(
if (holder.image == null) {
return
}
- SongGlideRequest.Builder.from(Glide.with(activity), song)
- .checkIgnoreMediaStore(activity)
- .generatePalette(activity).build()
+ Glide.with(activity)
+ .asBitmapPalette()
+ .songCoverOptions(song)
+ .load(RetroGlideExtension.getSongModel(song))
.into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(colors, holder)
@@ -112,14 +129,18 @@ open class SongAdapter(
})
}
- private fun getSongTitle(song: Song): String? {
+ private fun getSongTitle(song: Song): String {
return song.title
}
- private fun getSongText(song: Song): String? {
+ private fun getSongText(song: Song): String {
return song.artistName
}
+ private fun getSongText2(song: Song): String {
+ return song.albumName
+ }
+
override fun getItemCount(): Int {
return dataSet.size
}
@@ -128,26 +149,31 @@ open class SongAdapter(
return dataSet[position]
}
- override fun getName(song: Song): String {
- return song.title
+ override fun getName(model: Song): String {
+ return model.title
}
- override fun onMultipleItemAction(menuItem: MenuItem, selection: ArrayList) {
+ 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)
}
@@ -157,7 +183,6 @@ open class SongAdapter(
get() = dataSet[layoutPosition]
init {
- setImageTransitionName(activity.getString(R.string.transition_album_art))
menu?.setOnClickListener(object : SongMenuHelper.OnClickSongMenu(activity) {
override val song: Song
get() = this@ViewHolder.song
@@ -172,15 +197,14 @@ open class SongAdapter(
}
protected open fun onSongMenuItemClick(item: MenuItem): Boolean {
- if (image != null && image!!.visibility == View.VISIBLE) {
+ if (image != null && image!!.isVisible) {
when (item.itemId) {
R.id.action_go_to_album -> {
- val activityOptions = ActivityOptions.makeSceneTransitionAnimation(
- activity,
- imageContainerCard ?: image,
- activity.getString(R.string.transition_album_art)
- )
- NavigationUtil.goToAlbumOptions(activity, song.albumId, activityOptions)
+ activity.findNavController(R.id.fragment_container)
+ .navigate(
+ R.id.albumDetailsFragment,
+ bundleOf(EXTRA_ALBUM_ID to song.albumId)
+ )
return true
}
}
@@ -197,6 +221,7 @@ open class SongAdapter(
}
override fun onLongClick(v: View?): Boolean {
+ println("Long click")
return toggleChecked(layoutPosition)
}
}
diff --git a/app/src/main/java/io/github/muntashirakon/music/appshortcuts/AppShortcutIconGenerator.kt b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutIconGenerator.kt
similarity index 63%
rename from app/src/main/java/io/github/muntashirakon/music/appshortcuts/AppShortcutIconGenerator.kt
rename to app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutIconGenerator.kt
index 106d37337..ad8f964e7 100644
--- a/app/src/main/java/io/github/muntashirakon/music/appshortcuts/AppShortcutIconGenerator.kt
+++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutIconGenerator.kt
@@ -1,32 +1,30 @@
/*
- * Copyright (c) 2019 Hemanth Savarala.
+ * 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 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 io.github.muntashirakon.music.appshortcuts
+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.util.TypedValue
import androidx.annotation.RequiresApi
+import androidx.core.graphics.drawable.toBitmap
import code.name.monkey.appthemehelper.ThemeStore
-import io.github.muntashirakon.music.R
-import io.github.muntashirakon.music.util.PreferenceUtil
-import io.github.muntashirakon.music.util.RetroUtil
+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 {
@@ -60,28 +58,20 @@ object AppShortcutIconGenerator {
}
private fun generateThemedIcon(
- context: Context, iconId: Int, foregroundColor: Int, backgroundColor: Int
+ context: Context,
+ iconId: Int,
+ foregroundColor: Int,
+ backgroundColor: Int,
): Icon {
// Get and tint foreground and background drawables
- val vectorDrawable = RetroUtil.getTintedVectorDrawable(context, iconId, foregroundColor)
- val backgroundDrawable = RetroUtil.getTintedVectorDrawable(
- context, R.drawable.ic_app_shortcut_background, backgroundColor
- )
+ 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(drawableToBitmap(layerDrawable))
- }
-
- private fun drawableToBitmap(drawable: Drawable): Bitmap {
- val bitmap = Bitmap.createBitmap(
- drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888
- )
- val canvas = Canvas(bitmap)
- drawable.setBounds(0, 0, canvas.width, canvas.height)
- drawable.draw(canvas)
- return bitmap
+ return Icon.createWithBitmap(layerDrawable.toBitmap())
}
}
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/io/github/muntashirakon/music/appshortcuts/DynamicShortcutManager.kt b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/DynamicShortcutManager.kt
similarity index 53%
rename from app/src/main/java/io/github/muntashirakon/music/appshortcuts/DynamicShortcutManager.kt
rename to app/src/main/java/code/name/monkey/retromusic/appshortcuts/DynamicShortcutManager.kt
index 6cfcedba5..a943ed0c8 100644
--- a/app/src/main/java/io/github/muntashirakon/music/appshortcuts/DynamicShortcutManager.kt
+++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/DynamicShortcutManager.kt
@@ -1,18 +1,18 @@
/*
- * Copyright (c) 2019 Hemanth Savarala.
+ * 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 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 io.github.muntashirakon.music.appshortcuts
+package code.name.monkey.retromusic.appshortcuts
import android.annotation.TargetApi
import android.content.Context
@@ -21,34 +21,31 @@ import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon
import android.os.Build
-import io.github.muntashirakon.music.appshortcuts.shortcuttype.LastAddedShortcutType
-import io.github.muntashirakon.music.appshortcuts.shortcuttype.SearchShortCutType
-import io.github.muntashirakon.music.appshortcuts.shortcuttype.ShuffleAllShortcutType
-import io.github.muntashirakon.music.appshortcuts.shortcuttype.TopTracksShortcutType
-import java.util.*
+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(ShortcutManager::class.java)
+ private val shortcutManager: ShortcutManager? =
+ this.context.getSystemService()
private val defaultShortcuts: List
- get() = Arrays.asList(
- SearchShortCutType(context).shortcutInfo,
+ get() = listOf(
ShuffleAllShortcutType(context).shortcutInfo,
TopTracksShortcutType(context).shortcutInfo,
LastAddedShortcutType(context).shortcutInfo
-
)
fun initDynamicShortcuts() {
- //if (shortcutManager.dynamicShortcuts.size == 0) {
- shortcutManager.dynamicShortcuts = defaultShortcuts
- //}
+ // if (shortcutManager.dynamicShortcuts.size == 0) {
+ shortcutManager?.dynamicShortcuts = defaultShortcuts
+ // }
}
fun updateDynamicShortcuts() {
- shortcutManager.updateShortcuts(defaultShortcuts)
+ shortcutManager?.updateShortcuts(defaultShortcuts)
}
companion object {
@@ -61,12 +58,16 @@ class DynamicShortcutManager(private val context: Context) {
icon: Icon,
intent: Intent
): ShortcutInfo {
- return ShortcutInfo.Builder(context, id).setShortLabel(shortLabel)
- .setLongLabel(longLabel).setIcon(icon).setIntent(intent).build()
+ return ShortcutInfo.Builder(context, id)
+ .setShortLabel(shortLabel)
+ .setLongLabel(longLabel)
+ .setIcon(icon)
+ .setIntent(intent)
+ .build()
}
fun reportShortcutUsed(context: Context, shortcutId: String) {
- context.getSystemService(ShortcutManager::class.java).reportShortcutUsed(shortcutId)
+ context.getSystemService()?.reportShortcutUsed(shortcutId)
}
}
}
diff --git a/app/src/main/java/io/github/muntashirakon/music/appshortcuts/shortcuttype/BaseShortcutType.kt b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/BaseShortcutType.kt
similarity index 72%
rename from app/src/main/java/io/github/muntashirakon/music/appshortcuts/shortcuttype/BaseShortcutType.kt
rename to app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/BaseShortcutType.kt
index 5bf3d0ff7..4b0c7f3a0 100644
--- a/app/src/main/java/io/github/muntashirakon/music/appshortcuts/shortcuttype/BaseShortcutType.kt
+++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/BaseShortcutType.kt
@@ -1,26 +1,26 @@
/*
- * Copyright (c) 2019 Hemanth Savarala.
+ * 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 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 io.github.muntashirakon.music.appshortcuts.shortcuttype
+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 io.github.muntashirakon.music.appshortcuts.AppShortcutLauncherActivity
+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) {
@@ -33,11 +33,10 @@ abstract class BaseShortcutType(internal var context: Context) {
* @param shortcutType Describes the type of shortcut to create (ShuffleAll, TopTracks, custom playlist, etc.)
* @return
*/
- internal fun getPlaySongsIntent(shortcutType: Int): Intent {
+ internal fun getPlaySongsIntent(shortcutType: Long): Intent {
val intent = Intent(context, AppShortcutLauncherActivity::class.java)
intent.action = Intent.ACTION_VIEW
- val b = Bundle()
- b.putInt(AppShortcutLauncherActivity.KEY_SHORTCUT_TYPE, shortcutType)
+ val b = bundleOf(AppShortcutLauncherActivity.KEY_SHORTCUT_TYPE to shortcutType)
intent.putExtras(b)
return intent
}
diff --git a/app/src/main/java/io/github/muntashirakon/music/appshortcuts/shortcuttype/LastAddedShortcutType.kt b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/LastAddedShortcutType.kt
similarity index 72%
rename from app/src/main/java/io/github/muntashirakon/music/appshortcuts/shortcuttype/LastAddedShortcutType.kt
rename to app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/LastAddedShortcutType.kt
index 8ebfd1a8e..d0976042c 100644
--- a/app/src/main/java/io/github/muntashirakon/music/appshortcuts/shortcuttype/LastAddedShortcutType.kt
+++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/LastAddedShortcutType.kt
@@ -1,26 +1,26 @@
/*
- * Copyright (c) 2019 Hemanth Savarala.
+ * 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 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 io.github.muntashirakon.music.appshortcuts.shortcuttype
+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 io.github.muntashirakon.music.R
-import io.github.muntashirakon.music.appshortcuts.AppShortcutIconGenerator
-import io.github.muntashirakon.music.appshortcuts.AppShortcutLauncherActivity
+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) {
@@ -42,6 +42,6 @@ class LastAddedShortcutType(context: Context) : BaseShortcutType(context) {
companion object {
val id: String
- get() = BaseShortcutType.ID_PREFIX + "last_added"
+ get() = ID_PREFIX + "last_added"
}
}
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/io/github/muntashirakon/music/appshortcuts/shortcuttype/TopTracksShortcutType.kt b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/TopTracksShortcutType.kt
similarity index 71%
rename from app/src/main/java/io/github/muntashirakon/music/appshortcuts/shortcuttype/TopTracksShortcutType.kt
rename to app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/TopTracksShortcutType.kt
index 78ee8c352..fc0fa9f47 100644
--- a/app/src/main/java/io/github/muntashirakon/music/appshortcuts/shortcuttype/TopTracksShortcutType.kt
+++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/TopTracksShortcutType.kt
@@ -1,26 +1,25 @@
/*
- * Copyright (c) 2019 Hemanth Savarala.
+ * 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 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 io.github.muntashirakon.music.appshortcuts.shortcuttype
+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 io.github.muntashirakon.music.R
-import io.github.muntashirakon.music.appshortcuts.AppShortcutIconGenerator
-import io.github.muntashirakon.music.appshortcuts.AppShortcutLauncherActivity
+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) {
@@ -40,6 +39,6 @@ class TopTracksShortcutType(context: Context) : BaseShortcutType(context) {
companion object {
val id: String
- get() = BaseShortcutType.ID_PREFIX + "top_tracks"
+ get() = ID_PREFIX + "top_tracks"
}
}
diff --git a/app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetBig.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.kt
similarity index 62%
rename from app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetBig.kt
rename to app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.kt
index 0d2995b4f..7d9497209 100644
--- a/app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetBig.kt
+++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.kt
@@ -1,18 +1,18 @@
/*
- * Copyright (c) 2019 Hemanth Savarala.
+ * 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 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 io.github.muntashirakon.music.appwidgets
+package code.name.monkey.retromusic.appwidgets
import android.app.PendingIntent
import android.content.ComponentName
@@ -20,21 +20,26 @@ import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
-import android.text.TextUtils
import android.view.View
import android.widget.RemoteViews
+import androidx.core.graphics.drawable.toBitmap
import code.name.monkey.appthemehelper.util.MaterialValueHelper
-import io.github.muntashirakon.music.R
-import io.github.muntashirakon.music.activities.MainActivity
-import io.github.muntashirakon.music.appwidgets.base.BaseAppWidget
-import io.github.muntashirakon.music.glide.SongGlideRequest
-import io.github.muntashirakon.music.service.MusicService
-import io.github.muntashirakon.music.service.MusicService.*
-import io.github.muntashirakon.music.util.RetroUtil
+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.animation.GlideAnimation
-import com.bumptech.glide.request.target.SimpleTarget
+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
@@ -54,31 +59,24 @@ class AppWidgetBig : BaseAppWidget() {
)
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
appWidgetView.setImageViewBitmap(
- R.id.button_next, createBitmap(
- RetroUtil.getTintedVectorDrawable(
- context,
- R.drawable.ic_skip_next,
- MaterialValueHelper.getPrimaryTextColor(context, false)
- )!!, 1f
- )
+ R.id.button_next, context.getTintedDrawable(
+ R.drawable.ic_skip_next,
+ MaterialValueHelper.getPrimaryTextColor(context, false)
+ ).toBitmap()
)
appWidgetView.setImageViewBitmap(
- R.id.button_prev, createBitmap(
- RetroUtil.getTintedVectorDrawable(
- context,
- R.drawable.ic_skip_previous,
- MaterialValueHelper.getPrimaryTextColor(context, false)
- )!!, 1f
- )
+ R.id.button_prev,
+ context.getTintedDrawable(
+ R.drawable.ic_skip_previous,
+ MaterialValueHelper.getPrimaryTextColor(context, false)
+ ).toBitmap()
)
appWidgetView.setImageViewBitmap(
- R.id.button_toggle_play_pause, BaseAppWidget.Companion.createBitmap(
- RetroUtil.getTintedVectorDrawable(
- context,
- R.drawable.ic_play_arrow_white_32dp,
- MaterialValueHelper.getPrimaryTextColor(context, false)
- )!!, 1f
- )
+ R.id.button_toggle_play_pause,
+ context.getTintedDrawable(
+ R.drawable.ic_play_arrow_white_32dp,
+ MaterialValueHelper.getPrimaryTextColor(context, false)
+ ).toBitmap()
)
linkButtons(context, appWidgetView)
@@ -97,7 +95,7 @@ class AppWidgetBig : BaseAppWidget() {
val song = service.currentSong
// Set the titles and artwork
- if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
+ if (song.title.isEmpty() && song.artistName.isEmpty()) {
appWidgetView.setViewVisibility(
R.id.media_titles,
View.INVISIBLE
@@ -119,33 +117,27 @@ class AppWidgetBig : BaseAppWidget() {
val playPauseRes =
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play_arrow_white_32dp
appWidgetView.setImageViewBitmap(
- R.id.button_toggle_play_pause, createBitmap(
- RetroUtil.getTintedVectorDrawable(
- service,
- playPauseRes,
- primaryColor
- )!!, 1f
- )
+ R.id.button_toggle_play_pause,
+ service.getTintedDrawable(
+ playPauseRes,
+ primaryColor
+ ).toBitmap()
)
// Set prev/next button drawables
appWidgetView.setImageViewBitmap(
- R.id.button_next, createBitmap(
- RetroUtil.getTintedVectorDrawable(
- service,
- R.drawable.ic_skip_next,
- primaryColor
- )!!, 1f
- )
+ R.id.button_next,
+ service.getTintedDrawable(
+ R.drawable.ic_skip_next,
+ primaryColor
+ ).toBitmap()
)
appWidgetView.setImageViewBitmap(
- R.id.button_prev, createBitmap(
- RetroUtil.getTintedVectorDrawable(
- service,
- R.drawable.ic_skip_previous,
- primaryColor
- )!!, 1f
- )
+ R.id.button_prev,
+ service.getTintedDrawable(
+ R.drawable.ic_skip_previous,
+ primaryColor
+ ).toBitmap()
)
// Link actions buttons to intents
@@ -153,27 +145,31 @@ class AppWidgetBig : BaseAppWidget() {
// Load the album cover async and push the update on completion
val p = RetroUtil.getScreenSize(service)
- val widgetImageSize = Math.min(p.x, p.y)
+ val widgetImageSize = p.x.coerceAtMost(p.y)
val appContext = service.applicationContext
service.runOnUiThread {
if (target != null) {
- Glide.clear(target)
+ Glide.with(service).clear(target)
}
- target = SongGlideRequest.Builder.from(Glide.with(appContext), song)
- .checkIgnoreMediaStore(appContext).asBitmap().build()
- .into(object : SimpleTarget(widgetImageSize, widgetImageSize) {
+ target = Glide.with(appContext)
+ .asBitmap()
+ //.checkIgnoreMediaStore()
+ .load(RetroGlideExtension.getSongModel(song))
+ .into(object : CustomTarget(widgetImageSize, widgetImageSize) {
override fun onResourceReady(
resource: Bitmap,
- glideAnimation: GlideAnimation
+ transition: Transition?,
) {
update(resource)
}
- override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) {
- super.onLoadFailed(e, errorDrawable)
+ override fun onLoadFailed(errorDrawable: Drawable?) {
+ super.onLoadFailed(errorDrawable)
update(null)
}
+ override fun onLoadCleared(placeholder: Drawable?) {}
+
private fun update(bitmap: Bitmap?) {
if (bitmap == null) {
appWidgetView.setImageViewResource(
@@ -193,15 +189,22 @@ class AppWidgetBig : BaseAppWidget() {
* 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, true)
- var pendingIntent: PendingIntent
+ 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
- pendingIntent = PendingIntent.getActivity(context, 0, action, 0)
+ var pendingIntent =
+ PendingIntent.getActivity(
+ context, 0, action, if (VersionUtils.hasMarshmallow())
+ PendingIntent.FLAG_IMMUTABLE
+ else 0
+ )
views.setOnClickPendingIntent(R.id.clickable_area, pendingIntent)
// Previous track
@@ -229,6 +232,5 @@ class AppWidgetBig : BaseAppWidget() {
}
return mInstance!!
}
-
}
}
diff --git a/app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetCard.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.kt
similarity index 60%
rename from app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetCard.kt
rename to app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.kt
index 056da1874..471d908d6 100644
--- a/app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetCard.kt
+++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.kt
@@ -1,18 +1,18 @@
/*
- * Copyright (c) 2019 Hemanth Savarala.
+ * 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 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 io.github.muntashirakon.music.appwidgets
+package code.name.monkey.retromusic.appwidgets
import android.app.PendingIntent
import android.content.ComponentName
@@ -20,23 +20,28 @@ import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
-import android.text.TextUtils
import android.view.View
import android.widget.RemoteViews
+import androidx.core.graphics.drawable.toBitmap
import code.name.monkey.appthemehelper.util.MaterialValueHelper
-import io.github.muntashirakon.music.R
-import io.github.muntashirakon.music.activities.MainActivity
-import io.github.muntashirakon.music.appwidgets.base.BaseAppWidget
-import io.github.muntashirakon.music.glide.SongGlideRequest
-import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper
-import io.github.muntashirakon.music.service.MusicService
-import io.github.muntashirakon.music.service.MusicService.*
-import io.github.muntashirakon.music.util.ImageUtil
-import io.github.muntashirakon.music.util.RetroUtil
+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.animation.GlideAnimation
-import com.bumptech.glide.request.target.SimpleTarget
+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
@@ -52,31 +57,25 @@ class AppWidgetCard : BaseAppWidget() {
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
val secondaryColor = MaterialValueHelper.getSecondaryTextColor(context, true)
appWidgetView.setImageViewBitmap(
- R.id.button_next, createBitmap(
- RetroUtil.getTintedVectorDrawable(
- context,
- R.drawable.ic_skip_next,
- secondaryColor
- )!!, 1f
- )
+ R.id.button_next,
+ context.getTintedDrawable(
+ R.drawable.ic_skip_next,
+ secondaryColor
+ ).toBitmap()
)
appWidgetView.setImageViewBitmap(
- R.id.button_prev, createBitmap(
- RetroUtil.getTintedVectorDrawable(
- context,
- R.drawable.ic_skip_previous,
- secondaryColor
- )!!, 1f
- )
+ R.id.button_prev,
+ context.getTintedDrawable(
+ R.drawable.ic_skip_previous,
+ secondaryColor
+ ).toBitmap()
)
appWidgetView.setImageViewBitmap(
- R.id.button_toggle_play_pause, createBitmap(
- RetroUtil.getTintedVectorDrawable(
- context,
- R.drawable.ic_play_arrow_white_32dp,
- secondaryColor
- )!!, 1f
- )
+ R.id.button_toggle_play_pause,
+ context.getTintedDrawable(
+ R.drawable.ic_play_arrow_white_32dp,
+ secondaryColor
+ ).toBitmap()
)
linkButtons(context, appWidgetView)
@@ -93,7 +92,7 @@ class AppWidgetCard : BaseAppWidget() {
val song = service.currentSong
// Set the titles and artwork
- if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
+ if (song.title.isEmpty() && song.artistName.isEmpty()) {
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
} else {
appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE)
@@ -105,33 +104,27 @@ class AppWidgetCard : BaseAppWidget() {
val playPauseRes =
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play_arrow_white_32dp
appWidgetView.setImageViewBitmap(
- R.id.button_toggle_play_pause, createBitmap(
- RetroUtil.getTintedVectorDrawable(
- service,
- playPauseRes,
- MaterialValueHelper.getSecondaryTextColor(service, true)
- )!!, 1f
- )
+ 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, createBitmap(
- RetroUtil.getTintedVectorDrawable(
- service,
- R.drawable.ic_skip_next,
- MaterialValueHelper.getSecondaryTextColor(service, true)
- )!!, 1f
- )
+ R.id.button_next,
+ service.getTintedDrawable(
+ R.drawable.ic_skip_next,
+ MaterialValueHelper.getSecondaryTextColor(service, true)
+ ).toBitmap()
)
appWidgetView.setImageViewBitmap(
- R.id.button_prev, createBitmap(
- RetroUtil.getTintedVectorDrawable(
- service,
- R.drawable.ic_skip_previous,
- MaterialValueHelper.getSecondaryTextColor(service, true)
- )!!, 1f
- )
+ R.id.button_prev,
+ service.getTintedDrawable(
+ R.drawable.ic_skip_previous,
+ MaterialValueHelper.getSecondaryTextColor(service, true)
+ ).toBitmap()
)
// Link actions buttons to intents
@@ -149,14 +142,17 @@ class AppWidgetCard : BaseAppWidget() {
// Load the album cover async and push the update on completion
service.runOnUiThread {
if (target != null) {
- Glide.clear(target)
+ Glide.with(service).clear(target)
}
- target = SongGlideRequest.Builder.from(Glide.with(service), song)
- .checkIgnoreMediaStore(service).generatePalette(service).build().centerCrop()
- .into(object : SimpleTarget(imageSize, imageSize) {
+ target = Glide.with(service)
+ .asBitmapPalette()
+ .songCoverOptions(song)
+ .load(RetroGlideExtension.getSongModel(song))
+ .centerCrop()
+ .into(object : CustomTarget(imageSize, imageSize) {
override fun onResourceReady(
resource: BitmapPaletteWrapper,
- glideAnimation: GlideAnimation
+ transition: Transition?,
) {
val palette = resource.palette
update(
@@ -170,38 +166,31 @@ class AppWidgetCard : BaseAppWidget() {
)
}
- override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) {
- super.onLoadFailed(e, errorDrawable)
+ override fun onLoadFailed(errorDrawable: Drawable?) {
+ super.onLoadFailed(errorDrawable)
update(null, MaterialValueHelper.getSecondaryTextColor(service, true))
}
+ 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, ImageUtil.createBitmap(
- ImageUtil.getTintedVectorDrawable(
- service, playPauseRes, color
- )
- )
+ R.id.button_toggle_play_pause,
+ service.getTintedDrawable(playPauseRes, color).toBitmap()
)
// Set prev/next button drawables
appWidgetView.setImageViewBitmap(
- R.id.button_next, ImageUtil.createBitmap(
- ImageUtil.getTintedVectorDrawable(
- service, R.drawable.ic_skip_next, color
- )
- )
+ R.id.button_next,
+ service.getTintedDrawable(R.drawable.ic_skip_next, color).toBitmap()
)
appWidgetView.setImageViewBitmap(
- R.id.button_prev, ImageUtil.createBitmap(
- ImageUtil.getTintedVectorDrawable(
- service, R.drawable.ic_skip_previous, color
- )
- )
+ R.id.button_prev,
+ service.getTintedDrawable(R.drawable.ic_skip_previous, color).toBitmap()
)
- val image = getAlbumArtDrawable(service.resources, bitmap)
+ val image = getAlbumArtDrawable(service, bitmap)
val roundedBitmap = createRoundedBitmap(
image, imageSize, imageSize, cardRadius, 0F, cardRadius, 0F
)
@@ -217,14 +206,22 @@ class AppWidgetCard : BaseAppWidget() {
* Link up various button actions using [PendingIntent].
*/
private fun linkButtons(context: Context, views: RemoteViews) {
- val action: Intent = Intent(context, MainActivity::class.java).putExtra("expand", true)
- var pendingIntent: PendingIntent
+ 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
- pendingIntent = PendingIntent.getActivity(context, 0, action, 0)
+ 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)
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/io/github/muntashirakon/music/appwidgets/AppWidgetClassic.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.kt
similarity index 65%
rename from app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetClassic.kt
rename to app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.kt
index 9012c7be9..ad04f8aae 100644
--- a/app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetClassic.kt
+++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.kt
@@ -1,18 +1,18 @@
/*
- * Copyright (c) 2019 Hemanth Savarala.
+ * 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 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 io.github.muntashirakon.music.appwidgets
+package code.name.monkey.retromusic.appwidgets
import android.app.PendingIntent
import android.content.ComponentName
@@ -21,23 +21,28 @@ import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.drawable.Drawable
-import android.text.TextUtils
import android.view.View
import android.widget.RemoteViews
+import androidx.core.graphics.drawable.toBitmap
import code.name.monkey.appthemehelper.util.MaterialValueHelper
-import io.github.muntashirakon.music.R
-import io.github.muntashirakon.music.activities.MainActivity
-import io.github.muntashirakon.music.appwidgets.base.BaseAppWidget
-import io.github.muntashirakon.music.glide.SongGlideRequest
-import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper
-import io.github.muntashirakon.music.service.MusicService
-import io.github.muntashirakon.music.service.MusicService.*
-import io.github.muntashirakon.music.util.ImageUtil
-import io.github.muntashirakon.music.util.RetroUtil
+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.animation.GlideAnimation
-import com.bumptech.glide.request.target.SimpleTarget
+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
@@ -49,38 +54,31 @@ class AppWidgetClassic : BaseAppWidget() {
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,
- createBitmap(
- RetroUtil.getTintedVectorDrawable(
- context,
- R.drawable.ic_skip_next,
- MaterialValueHelper.getSecondaryTextColor(context, true)
- )!!, 1f
- )
+
+ context.getTintedDrawable(
+ R.drawable.ic_skip_next,
+ MaterialValueHelper.getSecondaryTextColor(context, true)
+ ).toBitmap()
)
appWidgetView.setImageViewBitmap(
R.id.button_prev,
- createBitmap(
- RetroUtil.getTintedVectorDrawable(
- context,
- R.drawable.ic_skip_previous,
- MaterialValueHelper.getSecondaryTextColor(context, true)
- )!!, 1f
- )
+
+ context.getTintedDrawable(
+ R.drawable.ic_skip_previous,
+ MaterialValueHelper.getSecondaryTextColor(context, true)
+ ).toBitmap()
)
appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause,
- createBitmap(
- RetroUtil.getTintedVectorDrawable(
- context,
- R.drawable.ic_play_arrow_white_32dp,
- MaterialValueHelper.getSecondaryTextColor(context, true)
- )!!, 1f
- )
+
+ context.getTintedDrawable(
+ R.drawable.ic_play_arrow_white_32dp,
+ MaterialValueHelper.getSecondaryTextColor(context, true)
+ ).toBitmap()
)
linkButtons(context, appWidgetView)
@@ -97,7 +95,7 @@ class AppWidgetClassic : BaseAppWidget() {
val song = service.currentSong
// Set the titles and artwork
- if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
+ if (song.title.isEmpty() && song.artistName.isEmpty()) {
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
} else {
appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE)
@@ -120,14 +118,18 @@ class AppWidgetClassic : BaseAppWidget() {
val appContext = service.applicationContext
service.runOnUiThread {
if (target != null) {
- Glide.clear(target)
+ Glide.with(service).clear(target)
}
- target = SongGlideRequest.Builder.from(Glide.with(service), song)
- .checkIgnoreMediaStore(service).generatePalette(service).build().centerCrop()
- .into(object : SimpleTarget(imageSize, imageSize) {
+ target = Glide.with(service)
+ .asBitmapPalette()
+ .songCoverOptions(song)
+ .load(RetroGlideExtension.getSongModel(song))
+ //.checkIgnoreMediaStore()
+ .centerCrop()
+ .into(object : CustomTarget(imageSize, imageSize) {
override fun onResourceReady(
resource: BitmapPaletteWrapper,
- glideAnimation: GlideAnimation
+ transition: Transition?,
) {
val palette = resource.palette
update(
@@ -143,49 +145,42 @@ class AppWidgetClassic : BaseAppWidget() {
)
}
- override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) {
- super.onLoadFailed(e, errorDrawable)
+ override fun onLoadFailed(errorDrawable: Drawable?) {
+ super.onLoadFailed(errorDrawable)
update(null, Color.WHITE)
}
+ 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,
- ImageUtil.createBitmap(
- ImageUtil.getTintedVectorDrawable(
- service,
- playPauseRes,
- color
- )
- )
+ service.getTintedDrawable(
+ playPauseRes,
+ color
+ ).toBitmap()
)
// Set prev/next button drawables
appWidgetView.setImageViewBitmap(
R.id.button_next,
- ImageUtil.createBitmap(
- ImageUtil.getTintedVectorDrawable(
- service,
- R.drawable.ic_skip_next,
- color
- )
- )
+ service.getTintedDrawable(
+ R.drawable.ic_skip_next,
+ color
+ ).toBitmap()
)
appWidgetView.setImageViewBitmap(
R.id.button_prev,
- ImageUtil.createBitmap(
- ImageUtil.getTintedVectorDrawable(
- service,
- R.drawable.ic_skip_previous,
- color
- )
- )
+ service.getTintedDrawable(
+ R.drawable.ic_skip_previous,
+ color
+ ).toBitmap()
)
- val image = getAlbumArtDrawable(service.resources, bitmap)
+ val image = getAlbumArtDrawable(service, bitmap)
val roundedBitmap =
createRoundedBitmap(
image,
@@ -208,14 +203,21 @@ class AppWidgetClassic : BaseAppWidget() {
* Link up various button actions using [PendingIntent].
*/
private fun linkButtons(context: Context, views: RemoteViews) {
- val action = Intent(context, MainActivity::class.java).putExtra("expand", true)
- var pendingIntent: PendingIntent
+ 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
- pendingIntent = PendingIntent.getActivity(context, 0, action, 0)
+ 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)
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/io/github/muntashirakon/music/appwidgets/AppWidgetSmall.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.kt
similarity index 63%
rename from app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetSmall.kt
rename to app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.kt
index 848d3cbdc..ee4e87e99 100644
--- a/app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetSmall.kt
+++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.kt
@@ -1,18 +1,18 @@
/*
- * Copyright (c) 2019 Hemanth Savarala.
+ * 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 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 io.github.muntashirakon.music.appwidgets
+package code.name.monkey.retromusic.appwidgets
import android.app.PendingIntent
import android.content.ComponentName
@@ -20,22 +20,28 @@ import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
-import android.text.TextUtils
import android.view.View
import android.widget.RemoteViews
+import androidx.core.graphics.drawable.toBitmap
import code.name.monkey.appthemehelper.util.MaterialValueHelper
-import io.github.muntashirakon.music.R
-import io.github.muntashirakon.music.activities.MainActivity
-import io.github.muntashirakon.music.appwidgets.base.BaseAppWidget
-import io.github.muntashirakon.music.glide.SongGlideRequest
-import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper
-import io.github.muntashirakon.music.service.MusicService
-import io.github.muntashirakon.music.service.MusicService.*
-import io.github.muntashirakon.music.util.RetroUtil
+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.animation.GlideAnimation
-import com.bumptech.glide.request.target.SimpleTarget
+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
@@ -51,33 +57,26 @@ class AppWidgetSmall : BaseAppWidget() {
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
appWidgetView.setImageViewBitmap(
R.id.button_next,
- createBitmap(
- RetroUtil.getTintedVectorDrawable(
- context,
- R.drawable.ic_skip_next,
- MaterialValueHelper.getSecondaryTextColor(context, true)
- )!!, 1f
- )
+ context.getTintedDrawable(
+ R.drawable.ic_skip_next,
+ MaterialValueHelper.getSecondaryTextColor(context, true)
+ ).toBitmap()
)
appWidgetView.setImageViewBitmap(
R.id.button_prev,
- createBitmap(
- RetroUtil.getTintedVectorDrawable(
- context,
- R.drawable.ic_skip_previous,
- MaterialValueHelper.getSecondaryTextColor(context, true)
- )!!, 1f
- )
+
+ context.getTintedDrawable(
+ R.drawable.ic_skip_previous,
+ MaterialValueHelper.getSecondaryTextColor(context, true)
+ ).toBitmap()
)
appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause,
- createBitmap(
- RetroUtil.getTintedVectorDrawable(
- context,
- R.drawable.ic_play_arrow_white_32dp,
- MaterialValueHelper.getSecondaryTextColor(context, true)
- )!!, 1f
- )
+
+ context.getTintedDrawable(
+ R.drawable.ic_play_arrow_white_32dp,
+ MaterialValueHelper.getSecondaryTextColor(context, true)
+ ).toBitmap()
)
linkButtons(context, appWidgetView)
@@ -94,10 +93,10 @@ class AppWidgetSmall : BaseAppWidget() {
val song = service.currentSong
// Set the titles and artwork
- if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
+ if (song.title.isEmpty() && song.artistName.isEmpty()) {
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
} else {
- if (TextUtils.isEmpty(song.title) || TextUtils.isEmpty(song.artistName)) {
+ if (song.title.isEmpty() || song.artistName.isEmpty()) {
appWidgetView.setTextViewText(R.id.text_separator, "")
} else {
appWidgetView.setTextViewText(R.id.text_separator, "•")
@@ -122,14 +121,18 @@ class AppWidgetSmall : BaseAppWidget() {
val appContext = service.applicationContext
service.runOnUiThread {
if (target != null) {
- Glide.clear(target)
+ Glide.with(service).clear(target)
}
- target = SongGlideRequest.Builder.from(Glide.with(service), song)
- .checkIgnoreMediaStore(service).generatePalette(service).build().centerCrop()
- .into(object : SimpleTarget(imageSize, imageSize) {
+ target = Glide.with(service)
+ .asBitmapPalette()
+ .songCoverOptions(song)
+ //.checkIgnoreMediaStore()
+ .load(RetroGlideExtension.getSongModel(song))
+ .centerCrop()
+ .into(object : CustomTarget(imageSize, imageSize) {
override fun onResourceReady(
resource: BitmapPaletteWrapper,
- glideAnimation: GlideAnimation
+ transition: Transition?,
) {
val palette = resource.palette
update(
@@ -143,8 +146,12 @@ class AppWidgetSmall : BaseAppWidget() {
)
}
- override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) {
- super.onLoadFailed(e, errorDrawable)
+ override fun onLoadFailed(errorDrawable: Drawable?) {
+ super.onLoadFailed(errorDrawable)
+ update(null, MaterialValueHelper.getSecondaryTextColor(service, true))
+ }
+
+ override fun onLoadCleared(placeholder: Drawable?) {
update(null, MaterialValueHelper.getSecondaryTextColor(service, true))
}
@@ -153,30 +160,21 @@ class AppWidgetSmall : BaseAppWidget() {
val playPauseRes = if (isPlaying) R.drawable.ic_pause
else R.drawable.ic_play_arrow_white_32dp
appWidgetView.setImageViewBitmap(
- R.id.button_toggle_play_pause, createBitmap(
- RetroUtil.getTintedVectorDrawable(
- service, playPauseRes, color
- )!!, 1f
- )
+ R.id.button_toggle_play_pause,
+ service.getTintedDrawable(playPauseRes, color).toBitmap()
)
// Set prev/next button drawables
appWidgetView.setImageViewBitmap(
- R.id.button_next, createBitmap(
- RetroUtil.getTintedVectorDrawable(
- service, R.drawable.ic_skip_next, color
- )!!, 1f
- )
+ R.id.button_next,
+ service.getTintedDrawable(R.drawable.ic_skip_next, color).toBitmap()
)
appWidgetView.setImageViewBitmap(
- R.id.button_prev, createBitmap(
- RetroUtil.getTintedVectorDrawable(
- service, R.drawable.ic_skip_previous, color
- )!!, 1f
- )
+ R.id.button_prev,
+ service.getTintedDrawable(R.drawable.ic_skip_previous, color).toBitmap()
)
- val image = getAlbumArtDrawable(service.resources, bitmap)
+ val image = getAlbumArtDrawable(service, bitmap)
val roundedBitmap = createRoundedBitmap(
image, imageSize, imageSize, cardRadius, 0f, 0f, 0f
)
@@ -192,14 +190,22 @@ class AppWidgetSmall : BaseAppWidget() {
* Link up various button actions using [PendingIntent].
*/
private fun linkButtons(context: Context, views: RemoteViews) {
- val action = Intent(context, MainActivity::class.java).putExtra("expand", true)
- var pendingIntent: PendingIntent
+ 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
- pendingIntent = PendingIntent.getActivity(context, 0, action, 0)
+ 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)
diff --git a/app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetText.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetText.kt
similarity index 55%
rename from app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetText.kt
rename to app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetText.kt
index db28445e8..b7abb6ee5 100644
--- a/app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetText.kt
+++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetText.kt
@@ -1,72 +1,68 @@
/*
- * Copyright (c) 2019 Hemanth Savarala.
+ * 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 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 io.github.muntashirakon.music.appwidgets
+package code.name.monkey.retromusic.appwidgets
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.Intent
-import android.text.TextUtils
import android.view.View
import android.widget.RemoteViews
import androidx.core.content.ContextCompat
-import io.github.muntashirakon.music.App
-import io.github.muntashirakon.music.R
-import io.github.muntashirakon.music.activities.MainActivity
-import io.github.muntashirakon.music.appwidgets.base.BaseAppWidget
-import io.github.muntashirakon.music.service.MusicService
-import io.github.muntashirakon.music.service.MusicService.*
-import io.github.muntashirakon.music.util.RetroUtil
+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, createBitmap(
- RetroUtil.getTintedVectorDrawable(
- context, R.drawable.ic_skip_next, ContextCompat.getColor(
- context, R.color.md_white_1000
- )
- )!!, 1f
- )
+ 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, createBitmap(
- RetroUtil.getTintedVectorDrawable(
- context, R.drawable.ic_skip_previous, ContextCompat.getColor(
- context, R.color.md_white_1000
- )
- )!!, 1f
+ 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, createBitmap(
- RetroUtil.getTintedVectorDrawable(
- context, R.drawable.ic_play_arrow_white_32dp, ContextCompat.getColor(
- context, R.color.md_white_1000
- )
- )!!, 1f
+ 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, R.color.md_white_1000)
+ R.id.title, ContextCompat.getColor(context, code.name.monkey.appthemehelper.R.color.md_white_1000)
)
appWidgetView.setTextColor(
- R.id.text, ContextCompat.getColor(context, R.color.md_white_1000)
+ R.id.text, ContextCompat.getColor(context, code.name.monkey.appthemehelper.R.color.md_white_1000)
)
linkButtons(context, appWidgetView)
@@ -77,14 +73,21 @@ class AppWidgetText : BaseAppWidget() {
* Link up various button actions using [PendingIntent].
*/
private fun linkButtons(context: Context, views: RemoteViews) {
- val action = Intent(context, MainActivity::class.java).putExtra("expand", true)
- var pendingIntent: PendingIntent
+ 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
- pendingIntent = PendingIntent.getActivity(context, 0, action, 0)
+ 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)
@@ -108,7 +111,7 @@ class AppWidgetText : BaseAppWidget() {
val song = service.currentSong
// Set the titles and artwork
- if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
+ if (song.title.isEmpty() && song.artistName.isEmpty()) {
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
} else {
appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE)
@@ -122,41 +125,32 @@ class AppWidgetText : BaseAppWidget() {
val playPauseRes = if (isPlaying) R.drawable.ic_pause
else R.drawable.ic_play_arrow_white_32dp
appWidgetView.setImageViewBitmap(
- R.id.button_toggle_play_pause, createBitmap(
- RetroUtil.getTintedVectorDrawable(
- App.getContext(), playPauseRes, ContextCompat.getColor(
- App.getContext(), R.color.md_white_1000
- )
- )!!, 1f
- )
+ 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, createBitmap(
- RetroUtil.getTintedVectorDrawable(
- App.getContext(),
- R.drawable.ic_skip_next,
- ContextCompat.getColor(
- App.getContext(), R.color.md_white_1000
- )
- )!!, 1f
- )
+ 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, createBitmap(
- RetroUtil.getTintedVectorDrawable(
- App.getContext(),
- R.drawable.ic_skip_previous,
- ContextCompat.getColor(
- App.getContext(), R.color.md_white_1000
- )
- )!!, 1f
- )
+ 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 {
diff --git a/app/src/main/java/io/github/muntashirakon/music/appwidgets/BootReceiver.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/BootReceiver.kt
similarity index 86%
rename from app/src/main/java/io/github/muntashirakon/music/appwidgets/BootReceiver.kt
rename to app/src/main/java/code/name/monkey/retromusic/appwidgets/BootReceiver.kt
index bac3e580c..079e90ae6 100644
--- a/app/src/main/java/io/github/muntashirakon/music/appwidgets/BootReceiver.kt
+++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/BootReceiver.kt
@@ -1,18 +1,18 @@
/*
- * Copyright (c) 2019 Hemanth Savarala.
+ * 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 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 io.github.muntashirakon.music.appwidgets
+package code.name.monkey.retromusic.appwidgets
import android.appwidget.AppWidgetManager
import android.content.BroadcastReceiver
@@ -20,7 +20,7 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Build
-import io.github.muntashirakon.music.service.MusicService
+import code.name.monkey.retromusic.service.MusicService
class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
diff --git a/app/src/main/java/io/github/muntashirakon/music/appwidgets/base/BaseAppWidget.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/base/BaseAppWidget.kt
similarity index 73%
rename from app/src/main/java/io/github/muntashirakon/music/appwidgets/base/BaseAppWidget.kt
rename to app/src/main/java/code/name/monkey/retromusic/appwidgets/base/BaseAppWidget.kt
index a3bf6ff78..e460b7956 100644
--- a/app/src/main/java/io/github/muntashirakon/music/appwidgets/base/BaseAppWidget.kt
+++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/base/BaseAppWidget.kt
@@ -1,18 +1,18 @@
/*
- * Copyright (c) 2019 Hemanth Savarala.
+ * 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 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 io.github.muntashirakon.music.appwidgets.base
+package code.name.monkey.retromusic.appwidgets.base
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
@@ -20,19 +20,20 @@ import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
-import android.content.res.Resources
import android.graphics.*
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
-import android.os.Build
-import android.text.TextUtils
import android.widget.RemoteViews
import androidx.core.content.ContextCompat
-import io.github.muntashirakon.music.App
-import io.github.muntashirakon.music.R
-import io.github.muntashirakon.music.model.Song
-import io.github.muntashirakon.music.service.MusicService
-import io.github.muntashirakon.music.service.MusicService.*
+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() {
@@ -40,7 +41,9 @@ abstract class BaseAppWidget : AppWidgetProvider() {
* {@inheritDoc}
*/
override fun onUpdate(
- context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray
+ context: Context,
+ appWidgetManager: AppWidgetManager,
+ appWidgetIds: IntArray
) {
defaultAppWidget(context, appWidgetIds)
val updateIntent = Intent(APP_WIDGET_UPDATE)
@@ -55,14 +58,16 @@ abstract class BaseAppWidget : AppWidgetProvider() {
*/
fun notifyChange(service: MusicService, what: String) {
if (hasInstances(service)) {
- if (META_CHANGED == what || PLAY_STATE_CHANGED == what) {
+ if (META_CHANGED == what || PLAY_STATE_CHANGED == what || FAVORITE_STATE_CHANGED == what) {
performUpdate(service, null)
}
}
}
protected fun pushUpdate(
- context: Context, appWidgetIds: IntArray?, views: RemoteViews
+ context: Context,
+ appWidgetIds: IntArray?,
+ views: RemoteViews
) {
val appWidgetManager = AppWidgetManager.getInstance(context)
if (appWidgetIds != null) {
@@ -86,14 +91,20 @@ abstract class BaseAppWidget : AppWidgetProvider() {
}
protected fun buildPendingIntent(
- context: Context, action: String, serviceName: ComponentName
+ context: Context,
+ action: String,
+ serviceName: ComponentName
): PendingIntent {
val intent = Intent(action)
intent.component = serviceName
- return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- PendingIntent.getForegroundService(context, 0, intent, 0)
+ return if (VersionUtils.hasOreo()) {
+ PendingIntent.getForegroundService(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
} else {
- PendingIntent.getService(context, 0, intent, 0)
+ PendingIntent.getService(
+ context, 0, intent, if (VersionUtils.hasMarshmallow())
+ PendingIntent.FLAG_IMMUTABLE
+ else 0
+ )
}
}
@@ -101,18 +112,18 @@ abstract class BaseAppWidget : AppWidgetProvider() {
abstract fun performUpdate(service: MusicService, appWidgetIds: IntArray?)
- protected fun getAlbumArtDrawable(resources: Resources, bitmap: Bitmap?): Drawable {
+ protected fun getAlbumArtDrawable(context: Context, bitmap: Bitmap?): Drawable {
return if (bitmap == null) {
- ContextCompat.getDrawable(App.getContext(), R.drawable.default_audio_art)!!
+ ContextCompat.getDrawable(context, R.drawable.default_audio_art)!!
} else {
- BitmapDrawable(resources, bitmap)
+ BitmapDrawable(context.resources, bitmap)
}
}
protected fun getSongArtistAndAlbum(song: Song): String {
val builder = StringBuilder()
builder.append(song.artistName)
- if (!TextUtils.isEmpty(song.artistName) && !TextUtils.isEmpty(song.albumName)) {
+ if (song.artistName.isNotEmpty() && song.albumName.isNotEmpty()) {
builder.append(" • ")
}
builder.append(song.albumName)
@@ -156,20 +167,12 @@ abstract class BaseAppWidget : AppWidgetProvider() {
return rounded
}
- fun createBitmap(drawable: Drawable, sizeMultiplier: Float): Bitmap {
- val bitmap = Bitmap.createBitmap(
- (drawable.intrinsicWidth * sizeMultiplier).toInt(),
- (drawable.intrinsicHeight * sizeMultiplier).toInt(),
- Bitmap.Config.ARGB_8888
- )
- val c = Canvas(bitmap)
- drawable.setBounds(0, 0, c.width, c.height)
- drawable.draw(c)
- return bitmap
- }
-
protected fun composeRoundedRectPath(
- rect: RectF, tl: Float, tr: Float, bl: Float, br: Float
+ rect: RectF,
+ tl: Float,
+ tr: Float,
+ bl: Float,
+ br: Float
): Path {
val path = Path()
path.moveTo(rect.left + tl, rect.top)
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.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.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/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.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.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.kt
new file mode 100644
index 000000000..5fd45b060
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.kt
@@ -0,0 +1,162 @@
+/*
+ * 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.Activity
+import android.app.Dialog
+import android.content.Intent
+import android.os.Bundle
+import android.provider.MediaStore
+import androidx.activity.result.IntentSenderRequest
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.core.os.bundleOf
+import androidx.core.text.parseAsHtml
+import androidx.fragment.app.DialogFragment
+import code.name.monkey.appthemehelper.util.VersionUtils
+import code.name.monkey.retromusic.EXTRA_SONG
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.extensions.extraNotNull
+import code.name.monkey.retromusic.extensions.materialDialog
+import code.name.monkey.retromusic.fragments.LibraryViewModel
+import code.name.monkey.retromusic.fragments.ReloadType
+import code.name.monkey.retromusic.helper.MusicPlayerRemote
+import code.name.monkey.retromusic.model.Song
+import code.name.monkey.retromusic.util.MusicUtil
+import code.name.monkey.retromusic.util.SAFUtil
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import org.koin.androidx.viewmodel.ext.android.getViewModel
+
+class DeleteSongsDialog : DialogFragment() {
+ lateinit var libraryViewModel: LibraryViewModel
+
+ companion object {
+ fun create(song: Song): DeleteSongsDialog {
+ val list = ArrayList()
+ list.add(song)
+ return create(list)
+ }
+
+ fun create(songs: List): DeleteSongsDialog {
+ return DeleteSongsDialog().apply {
+ arguments = bundleOf(
+ EXTRA_SONG to ArrayList(songs)
+ )
+ }
+ }
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ libraryViewModel = activity?.getViewModel() as LibraryViewModel
+ val songs = extraNotNull>(EXTRA_SONG).value
+ if (VersionUtils.hasR()) {
+ val deleteResultLauncher =
+ registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result ->
+ if (result.resultCode == Activity.RESULT_OK) {
+ if ((songs.size == 1) && MusicPlayerRemote.isPlaying(songs[0])) {
+ MusicPlayerRemote.playNextSong()
+ }
+ MusicPlayerRemote.removeFromQueue(songs)
+ reloadTabs()
+ }
+ dismiss()
+ }
+ val pendingIntent =
+ MediaStore.createDeleteRequest(requireActivity().contentResolver, songs.map {
+ MusicUtil.getSongFileUri(it.id)
+ })
+ deleteResultLauncher.launch(
+ IntentSenderRequest.Builder(pendingIntent.intentSender).build()
+ )
+ return super.onCreateDialog(savedInstanceState)
+ } else {
+ val pair = if (songs.size > 1) {
+ Pair(
+ R.string.delete_songs_title,
+ String.format(getString(R.string.delete_x_songs), songs.size).parseAsHtml()
+ )
+ } else {
+ Pair(
+ R.string.delete_song_title,
+ String.format(getString(R.string.delete_song_x), songs[0].title).parseAsHtml()
+ )
+ }
+
+ return materialDialog()
+ .title(pair.first)
+ .message(text = pair.second)
+ .noAutoDismiss()
+ .negativeButton(android.R.string.cancel)
+ {
+ dismiss()
+ }
+ .positiveButton(R.string.action_delete)
+ {
+ if ((songs.size == 1) && MusicPlayerRemote.isPlaying(songs[0])) {
+ MusicPlayerRemote.playNextSong()
+ }
+ if (!SAFUtil.isSAFRequiredForSongs(songs)) {
+ CoroutineScope(Dispatchers.IO).launch {
+ dismiss()
+ MusicUtil.deleteTracks(requireContext(), songs)
+ reloadTabs()
+ }
+ } else {
+ if (SAFUtil.isSDCardAccessGranted(requireActivity())) {
+ deleteSongs(songs)
+ } else {
+ startActivityForResult(
+ Intent(requireActivity(), code.name.monkey.retromusic.activities.saf.SAFGuideActivity::class.java),
+ code.name.monkey.retromusic.activities.saf.SAFGuideActivity.REQUEST_CODE_SAF_GUIDE
+ )
+ }
+ }
+ }
+ }
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ when (requestCode) {
+ code.name.monkey.retromusic.activities.saf.SAFGuideActivity.REQUEST_CODE_SAF_GUIDE -> {
+ SAFUtil.openTreePicker(this)
+ }
+ SAFUtil.REQUEST_SAF_PICK_TREE,
+ SAFUtil.REQUEST_SAF_PICK_FILE -> {
+ if (resultCode == Activity.RESULT_OK) {
+ SAFUtil.saveTreeUri(requireActivity(), data)
+ val songs = extraNotNull>(EXTRA_SONG).value
+ deleteSongs(songs)
+ }
+ }
+ }
+ }
+
+ fun deleteSongs(songs: List) {
+ CoroutineScope(Dispatchers.IO).launch {
+ dismiss()
+ MusicUtil.deleteTracks(requireActivity(), songs, null, null)
+ reloadTabs()
+ }
+ }
+
+ private fun reloadTabs() {
+ libraryViewModel.forceReload(ReloadType.Songs)
+ libraryViewModel.forceReload(ReloadType.HomeSections)
+ libraryViewModel.forceReload(ReloadType.Artists)
+ libraryViewModel.forceReload(ReloadType.Albums)
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/ImportPlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/ImportPlaylistDialog.kt
new file mode 100644
index 000000000..87e3b6dbd
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/ImportPlaylistDialog.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.dialogs
+
+import android.app.Dialog
+import android.os.Bundle
+import androidx.fragment.app.DialogFragment
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.extensions.colorButtons
+import code.name.monkey.retromusic.extensions.materialDialog
+import code.name.monkey.retromusic.fragments.LibraryViewModel
+import org.koin.androidx.viewmodel.ext.android.activityViewModel
+
+class ImportPlaylistDialog : DialogFragment() {
+ private val libraryViewModel by activityViewModel()
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ return materialDialog(R.string.import_playlist)
+ .setMessage(R.string.import_playlist_message)
+ .setPositiveButton(R.string.import_label) { _, _ ->
+ try {
+ libraryViewModel.importPlaylists()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ .create()
+ .colorButtons()
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/PlaybackSpeedDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/PlaybackSpeedDialog.kt
new file mode 100644
index 000000000..6965d0831
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/PlaybackSpeedDialog.kt
@@ -0,0 +1,58 @@
+package code.name.monkey.retromusic.dialogs
+
+import android.app.Dialog
+import android.os.Bundle
+import androidx.fragment.app.DialogFragment
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.databinding.DialogPlaybackSpeedBinding
+import code.name.monkey.retromusic.extensions.accent
+import code.name.monkey.retromusic.extensions.colorButtons
+import code.name.monkey.retromusic.extensions.materialDialog
+import code.name.monkey.retromusic.util.PreferenceUtil
+import com.google.android.material.slider.Slider
+
+class PlaybackSpeedDialog : DialogFragment() {
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val binding = DialogPlaybackSpeedBinding.inflate(layoutInflater)
+ binding.playbackSpeedSlider.accent()
+ binding.playbackPitchSlider.accent()
+ binding.playbackSpeedSlider.addOnChangeListener(Slider.OnChangeListener { _, value, _ ->
+ binding.speedValue.text = "$value"
+ })
+ binding.playbackPitchSlider.addOnChangeListener(Slider.OnChangeListener { _, value, _ ->
+ binding.pitchValue.text = "$value"
+ })
+ binding.playbackSpeedSlider.value = PreferenceUtil.playbackSpeed
+ binding.playbackPitchSlider.value = PreferenceUtil.playbackPitch
+
+ return materialDialog(R.string.playback_settings)
+ .setNegativeButton(android.R.string.cancel, null)
+ .setPositiveButton(R.string.save) { _, _ ->
+ updatePlaybackAndPitch(
+ binding.playbackSpeedSlider.value,
+ binding.playbackPitchSlider.value
+ )
+ }
+ .setNeutralButton(R.string.reset_action) {_, _ ->
+ updatePlaybackAndPitch(
+ 1F,
+ 1F
+ )
+ }
+ .setView(binding.root)
+ .create()
+ .colorButtons()
+ }
+
+ private fun updatePlaybackAndPitch(speed: Float, pitch: Float) {
+ PreferenceUtil.playbackSpeed = speed
+ PreferenceUtil.playbackPitch = pitch
+ }
+
+ companion object {
+ fun newInstance(): PlaybackSpeedDialog {
+ return PlaybackSpeedDialog()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveSongFromPlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveSongFromPlaylistDialog.kt
new file mode 100644
index 000000000..cd94bc480
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveSongFromPlaylistDialog.kt
@@ -0,0 +1,76 @@
+/*
+ * 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_SONG
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.db.SongEntity
+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 org.koin.androidx.viewmodel.ext.android.activityViewModel
+
+class RemoveSongFromPlaylistDialog : DialogFragment() {
+ private val libraryViewModel by activityViewModel()
+
+ companion object {
+ fun create(song: SongEntity): RemoveSongFromPlaylistDialog {
+ val list = mutableListOf()
+ list.add(song)
+ return create(list)
+ }
+
+ fun create(songs: List): RemoveSongFromPlaylistDialog {
+ return RemoveSongFromPlaylistDialog().apply {
+ arguments = bundleOf(
+ EXTRA_SONG to songs
+ )
+ }
+ }
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val songs = extraNotNull>(EXTRA_SONG).value
+ val pair = if (songs.size > 1) {
+ Pair(
+ R.string.remove_songs_from_playlist_title,
+ String.format(getString(R.string.remove_x_songs_from_playlist), songs.size)
+ .parseAsHtml()
+ )
+ } else {
+ Pair(
+ R.string.remove_song_from_playlist_title,
+ String.format(
+ getString(R.string.remove_song_x_from_playlist),
+ songs[0].title
+ ).parseAsHtml()
+ )
+ }
+ return materialDialog(pair.first)
+ .setMessage(pair.second)
+ .setPositiveButton(R.string.remove_action) { _, _ ->
+ libraryViewModel.deleteSongsInPlaylist(songs)
+ }
+ .setNegativeButton(android.R.string.cancel, null)
+ .create()
+ .colorButtons()
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.kt
new file mode 100644
index 000000000..803315277
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.kt
@@ -0,0 +1,70 @@
+/*
+ * 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_PLAYLIST_ID
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.db.PlaylistEntity
+import code.name.monkey.retromusic.extensions.accentColor
+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 com.google.android.material.textfield.TextInputEditText
+import com.google.android.material.textfield.TextInputLayout
+import org.koin.androidx.viewmodel.ext.android.activityViewModel
+
+class RenamePlaylistDialog : DialogFragment() {
+
+ private val libraryViewModel by activityViewModel()
+
+ companion object {
+ fun create(playlistEntity: PlaylistEntity): RenamePlaylistDialog {
+ return RenamePlaylistDialog().apply {
+ arguments = bundleOf(
+ EXTRA_PLAYLIST_ID to playlistEntity
+ )
+ }
+ }
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val playlistEntity = extraNotNull(EXTRA_PLAYLIST_ID).value
+ val layout = layoutInflater.inflate(R.layout.dialog_playlist, null)
+ val inputEditText: TextInputEditText = layout.findViewById(R.id.actionNewPlaylist)
+ val nameContainer: TextInputLayout = layout.findViewById(R.id.actionNewPlaylistContainer)
+ nameContainer.accentColor()
+ inputEditText.setText(playlistEntity.playlistName)
+ return materialDialog(R.string.rename_playlist_title)
+ .setView(layout)
+ .setNegativeButton(android.R.string.cancel, null)
+ .setPositiveButton(R.string.action_rename) { _, _ ->
+ val name = inputEditText.text.toString()
+ if (name.isNotEmpty()) {
+ libraryViewModel.renameRoomPlaylist(playlistEntity.playListId, name)
+ libraryViewModel.forceReload(ReloadType.Playlists)
+ } else {
+ nameContainer.error = "Playlist name should'nt be empty"
+ }
+ }
+ .create()
+ .colorButtons()
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/SavePlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/SavePlaylistDialog.kt
new file mode 100644
index 000000000..157a1b221
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/SavePlaylistDialog.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.media.MediaScannerConnection
+import android.os.Bundle
+import android.widget.Toast
+import androidx.core.os.bundleOf
+import androidx.fragment.app.DialogFragment
+import androidx.lifecycle.lifecycleScope
+import code.name.monkey.appthemehelper.util.VersionUtils
+import code.name.monkey.retromusic.EXTRA_PLAYLIST
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.db.PlaylistWithSongs
+import code.name.monkey.retromusic.extensions.*
+import code.name.monkey.retromusic.helper.M3UWriter
+import code.name.monkey.retromusic.util.PlaylistsUtil
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+class SavePlaylistDialog : DialogFragment() {
+ companion object {
+ fun create(playlistWithSongs: PlaylistWithSongs): SavePlaylistDialog {
+ return SavePlaylistDialog().apply {
+ arguments = bundleOf(
+ EXTRA_PLAYLIST to playlistWithSongs
+ )
+ }
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val playlistWithSongs = extraNotNull(EXTRA_PLAYLIST).value
+
+ if (VersionUtils.hasR()) {
+ createNewFile(
+ "audio/mpegurl",
+ playlistWithSongs.playlistEntity.playlistName
+ ) { outputStream, data ->
+ try {
+ if (outputStream != null) {
+ lifecycleScope.launch(Dispatchers.IO) {
+ M3UWriter.writeIO(
+ outputStream,
+ playlistWithSongs
+ )
+ withContext(Dispatchers.Main) {
+ showToast(
+ requireContext().getString(R.string.saved_playlist_to,
+ data?.lastPathSegment),
+ Toast.LENGTH_LONG
+ )
+ dismiss()
+ }
+ }
+ }
+ } catch (e: Exception) {
+ showToast(
+ "Something went wrong : " + e.message
+ )
+ }
+ }
+ } else {
+ lifecycleScope.launch(Dispatchers.IO) {
+ val file = PlaylistsUtil.savePlaylistWithSongs(playlistWithSongs)
+ MediaScannerConnection.scanFile(
+ requireActivity(),
+ arrayOf(file.path),
+ null
+ ) { _, _ ->
+ }
+ withContext(Dispatchers.Main) {
+ showToast(
+ getString(R.string.saved_playlist_to, file),
+ Toast.LENGTH_LONG
+ )
+ dismiss()
+ }
+ }
+ }
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ return materialDialog(R.string.save_playlist_title)
+ .setView(R.layout.loading)
+ .create().colorButtons()
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/SleepTimerDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/SleepTimerDialog.kt
new file mode 100755
index 000000000..32de5ba6c
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/SleepTimerDialog.kt
@@ -0,0 +1,189 @@
+/*
+ * 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.AlarmManager
+import android.app.Dialog
+import android.app.PendingIntent
+import android.content.DialogInterface
+import android.content.Intent
+import android.os.Bundle
+import android.os.CountDownTimer
+import android.os.SystemClock
+import android.widget.CheckBox
+import android.widget.SeekBar
+import android.widget.TextView
+import android.widget.Toast
+import androidx.appcompat.app.AlertDialog
+import androidx.core.content.getSystemService
+import androidx.core.view.isVisible
+import androidx.fragment.app.DialogFragment
+import code.name.monkey.appthemehelper.util.VersionUtils
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.databinding.DialogSleepTimerBinding
+import code.name.monkey.retromusic.extensions.addAccentColor
+import code.name.monkey.retromusic.extensions.materialDialog
+import code.name.monkey.retromusic.helper.MusicPlayerRemote
+import code.name.monkey.retromusic.service.MusicService
+import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_PENDING_QUIT
+import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_QUIT
+import code.name.monkey.retromusic.util.MusicUtil
+import code.name.monkey.retromusic.util.PreferenceUtil
+
+class SleepTimerDialog : DialogFragment() {
+
+ private var seekArcProgress: Int = 0
+ private lateinit var timerUpdater: TimerUpdater
+ private lateinit var dialog: AlertDialog
+
+ private var _binding: DialogSleepTimerBinding? = null
+ private val binding get() = _binding!!
+
+ private val shouldFinishLastSong: CheckBox get() = binding.shouldFinishLastSong
+ private val seekBar: SeekBar get() = binding.seekBar
+ private val timerDisplay: TextView get() = binding.timerDisplay
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ timerUpdater = TimerUpdater()
+ _binding = DialogSleepTimerBinding.inflate(layoutInflater)
+
+ val finishMusic = PreferenceUtil.isSleepTimerFinishMusic
+ shouldFinishLastSong.apply {
+ addAccentColor()
+ isChecked = finishMusic
+ }
+ seekBar.apply {
+ addAccentColor()
+ seekArcProgress = PreferenceUtil.lastSleepTimerValue
+ updateTimeDisplayTime()
+ progress = seekArcProgress
+ }
+
+ binding.seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
+ override fun onProgressChanged(seekBar: SeekBar, i: Int, b: Boolean) {
+ if (i < 1) {
+ seekBar.progress = 1
+ return
+ }
+ seekArcProgress = i
+ updateTimeDisplayTime()
+ }
+
+ override fun onStartTrackingTouch(seekBar: SeekBar) {
+ }
+
+ override fun onStopTrackingTouch(seekBar: SeekBar) {
+ PreferenceUtil.lastSleepTimerValue = seekArcProgress
+ }
+ })
+
+ materialDialog(R.string.action_sleep_timer).apply {
+ if (PreferenceUtil.nextSleepTimerElapsedRealTime > System.currentTimeMillis()) {
+ seekBar.isVisible = false
+ shouldFinishLastSong.isVisible = false
+ timerUpdater.start()
+ setPositiveButton(android.R.string.ok, null)
+ setNegativeButton(R.string.action_cancel) { _, _ ->
+ timerUpdater.cancel()
+ val previous = makeTimerPendingIntent(PendingIntent.FLAG_NO_CREATE)
+ if (previous != null) {
+ val am = requireContext().getSystemService()
+ am?.cancel(previous)
+ previous.cancel()
+ Toast.makeText(
+ requireContext(),
+ requireContext().resources.getString(R.string.sleep_timer_canceled),
+ Toast.LENGTH_SHORT
+ ).show()
+ val musicService = MusicPlayerRemote.musicService
+ if (musicService != null && musicService.pendingQuit) {
+ musicService.pendingQuit = false
+ Toast.makeText(
+ requireContext(),
+ requireContext().resources.getString(R.string.sleep_timer_canceled),
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ }
+ } else {
+ seekBar.isVisible = true
+ shouldFinishLastSong.isVisible = true
+ setPositiveButton(R.string.action_set) { _, _ ->
+ PreferenceUtil.isSleepTimerFinishMusic = shouldFinishLastSong.isChecked
+ val minutes = seekArcProgress
+ val pi = makeTimerPendingIntent(PendingIntent.FLAG_CANCEL_CURRENT)
+ val nextSleepTimerElapsedTime =
+ SystemClock.elapsedRealtime() + minutes * 60 * 1000
+ PreferenceUtil.nextSleepTimerElapsedRealTime = nextSleepTimerElapsedTime.toInt()
+ val am = requireContext().getSystemService()
+ am?.setExact(
+ AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ nextSleepTimerElapsedTime,
+ pi
+ )
+
+ Toast.makeText(
+ requireContext(),
+ requireContext().resources.getString(R.string.sleep_timer_set, minutes),
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ setView(binding.root)
+ dialog = create()
+
+ }
+ return dialog
+ }
+
+ private fun updateTimeDisplayTime() {
+ timerDisplay.text = "$seekArcProgress min"
+ }
+
+ private fun makeTimerPendingIntent(flag: Int): PendingIntent? {
+ return PendingIntent.getService(
+ requireActivity(), 0, makeTimerIntent(), flag or if (VersionUtils.hasMarshmallow())
+ PendingIntent.FLAG_IMMUTABLE
+ else 0
+ )
+ }
+
+ private fun makeTimerIntent(): Intent {
+ val intent = Intent(requireActivity(), MusicService::class.java)
+ return if (shouldFinishLastSong.isChecked) {
+ intent.setAction(ACTION_PENDING_QUIT)
+ } else intent.setAction(ACTION_QUIT)
+ }
+
+ override fun onDismiss(dialog: DialogInterface) {
+ super.onDismiss(dialog)
+ timerUpdater.cancel()
+ _binding = null
+ }
+
+ private inner class TimerUpdater :
+ CountDownTimer(
+ PreferenceUtil.nextSleepTimerElapsedRealTime - SystemClock.elapsedRealtime(),
+ 1000
+ ) {
+
+ override fun onTick(millisUntilFinished: Long) {
+ timerDisplay.text = MusicUtil.getReadableDurationString(millisUntilFinished)
+ }
+
+ override fun onFinish() {}
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/SongDetailDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/SongDetailDialog.kt
new file mode 100644
index 000000000..19995db30
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/SongDetailDialog.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.dialogs
+
+import android.app.Dialog
+import android.content.Context
+import android.os.Bundle
+import android.text.Spanned
+import android.util.Log
+import androidx.core.os.BundleCompat
+import androidx.core.os.bundleOf
+import androidx.core.text.parseAsHtml
+import androidx.fragment.app.DialogFragment
+import code.name.monkey.retromusic.EXTRA_SONG
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.databinding.DialogFileDetailsBinding
+import code.name.monkey.retromusic.extensions.colorButtons
+import code.name.monkey.retromusic.extensions.materialDialog
+import code.name.monkey.retromusic.model.Song
+import code.name.monkey.retromusic.util.MusicUtil
+import org.jaudiotagger.audio.AudioFileIO
+import java.io.File
+
+class SongDetailDialog : DialogFragment() {
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val context: Context = requireContext()
+ val binding = DialogFileDetailsBinding.inflate(layoutInflater)
+
+ val song = BundleCompat.getParcelable(requireArguments(), EXTRA_SONG, Song::class.java)
+ with(binding) {
+ fileName.text = makeTextWithTitle(context, R.string.label_file_name, "-")
+ filePath.text = makeTextWithTitle(context, R.string.label_file_path, "-")
+ fileSize.text = makeTextWithTitle(context, R.string.label_file_size, "-")
+ fileFormat.text = makeTextWithTitle(context, R.string.label_file_format, "-")
+ trackLength.text = makeTextWithTitle(context, R.string.label_track_length, "-")
+ bitrate.text = makeTextWithTitle(context, R.string.label_bit_rate, "-")
+ samplingRate.text = makeTextWithTitle(context, R.string.label_sampling_rate, "-")
+ }
+
+ if (song != null) {
+ val songFile = File(song.data)
+ if (songFile.exists()) {
+ binding.fileName.text =
+ makeTextWithTitle(context, R.string.label_file_name, songFile.name)
+ binding.filePath.text =
+ makeTextWithTitle(context, R.string.label_file_path, songFile.absolutePath)
+
+ binding.dateModified.text = makeTextWithTitle(
+ context, R.string.label_last_modified,
+ MusicUtil.getDateModifiedString(songFile.lastModified())
+ )
+
+ binding.fileSize.text =
+ makeTextWithTitle(
+ context,
+ R.string.label_file_size,
+ getFileSizeString(songFile.length())
+ )
+ try {
+ val audioFile = AudioFileIO.read(songFile)
+ val audioHeader = audioFile.audioHeader
+
+ binding.fileFormat.text =
+ makeTextWithTitle(context, R.string.label_file_format, audioHeader.format)
+ binding.trackLength.text = makeTextWithTitle(
+ context,
+ R.string.label_track_length,
+ MusicUtil.getReadableDurationString((audioHeader.trackLength * 1000).toLong())
+ )
+ binding.bitrate.text = makeTextWithTitle(
+ context,
+ R.string.label_bit_rate,
+ audioHeader.bitRate + " kb/s"
+ )
+ binding.samplingRate.text =
+ makeTextWithTitle(
+ context,
+ R.string.label_sampling_rate,
+ audioHeader.sampleRate + " Hz"
+ )
+ } catch (e: Exception) {
+ Log.e(TAG, "error while reading the song file", e)
+ // fallback
+ binding.trackLength.text = makeTextWithTitle(
+ context,
+ R.string.label_track_length,
+ MusicUtil.getReadableDurationString(song.duration)
+ )
+ }
+ } else {
+ // fallback
+ binding.fileName.text =
+ makeTextWithTitle(context, R.string.label_file_name, song.title)
+ binding.trackLength.text = makeTextWithTitle(
+ context,
+ R.string.label_track_length,
+ MusicUtil.getReadableDurationString(song.duration)
+ )
+ }
+ }
+ return materialDialog(R.string.action_details)
+ .setPositiveButton(android.R.string.ok, null)
+ .setView(binding.root)
+ .create()
+ .colorButtons()
+ }
+
+ companion object {
+
+ val TAG: String = SongDetailDialog::class.java.simpleName
+
+ fun create(song: Song): SongDetailDialog {
+ return SongDetailDialog().apply {
+ arguments = bundleOf(
+ EXTRA_SONG to song
+ )
+ }
+ }
+
+ private fun makeTextWithTitle(context: Context, titleResId: Int, text: String?): Spanned {
+ return ("" + context.resources.getString(titleResId) + ": " + " " + text)
+ .parseAsHtml()
+ }
+
+ private fun getFileSizeString(sizeInBytes: Long): String {
+ val fileSizeInKB = sizeInBytes / 1024
+ val fileSizeInMB = fileSizeInKB / 1024
+ return "$fileSizeInMB MB"
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/muntashirakon/music/dialogs/SongShareDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/SongShareDialog.kt
similarity index 71%
rename from app/src/main/java/io/github/muntashirakon/music/dialogs/SongShareDialog.kt
rename to app/src/main/java/code/name/monkey/retromusic/dialogs/SongShareDialog.kt
index 20e063f7d..fcfad5a5d 100644
--- a/app/src/main/java/io/github/muntashirakon/music/dialogs/SongShareDialog.kt
+++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/SongShareDialog.kt
@@ -1,34 +1,36 @@
/*
- * Copyright (c) 2019 Hemanth Savarala.
+ * 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 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 io.github.muntashirakon.music.dialogs
+package code.name.monkey.retromusic.dialogs
import android.app.Dialog
import android.content.Intent
import android.os.Bundle
+import androidx.core.os.BundleCompat
+import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
-import io.github.muntashirakon.music.EXTRA_SONG
-import io.github.muntashirakon.music.R
-import io.github.muntashirakon.music.activities.ShareInstagramStory
-import io.github.muntashirakon.music.extensions.colorButtons
-import io.github.muntashirakon.music.extensions.materialDialog
-import io.github.muntashirakon.music.model.Song
-import io.github.muntashirakon.music.util.MusicUtil
+import code.name.monkey.retromusic.EXTRA_SONG
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.activities.ShareInstagramStory
+import code.name.monkey.retromusic.extensions.colorButtons
+import code.name.monkey.retromusic.extensions.materialDialog
+import code.name.monkey.retromusic.model.Song
+import code.name.monkey.retromusic.util.MusicUtil
class SongShareDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val song: Song? = requireArguments().getParcelable(EXTRA_SONG)
+ val song: Song? = BundleCompat.getParcelable(requireArguments(), EXTRA_SONG, Song::class.java)
val listening: String =
String.format(
getString(R.string.currently_listening_to_x_by_x),
@@ -45,6 +47,7 @@ class SongShareDialog : DialogFragment() {
) { _, which ->
withAction(which, song, listening)
}
+ .setNegativeButton(R.string.action_cancel, null)
.create()
.colorButtons()
}
@@ -58,8 +61,7 @@ class SongShareDialog : DialogFragment() {
0 -> {
startActivity(Intent.createChooser(song?.let {
MusicUtil.createShareSongFileIntent(
- it,
- requireContext()
+ requireContext(), it
)
}, null))
}
@@ -93,11 +95,11 @@ class SongShareDialog : DialogFragment() {
companion object {
fun create(song: Song): SongShareDialog {
- val dialog = SongShareDialog()
- val args = Bundle()
- args.putParcelable(EXTRA_SONG, song)
- dialog.arguments = args
- return dialog
+ return SongShareDialog().apply {
+ arguments = bundleOf(
+ EXTRA_SONG to song
+ )
+ }
}
}
}
diff --git a/app/src/main/java/code/name/monkey/retromusic/extensions/ActivityExtensions.kt b/app/src/main/java/code/name/monkey/retromusic/extensions/ActivityExtensions.kt
new file mode 100644
index 000000000..bf3d9d8cd
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/extensions/ActivityExtensions.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.extensions
+
+import android.app.Activity
+import android.content.Intent
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.DimenRes
+import androidx.appcompat.app.AppCompatActivity
+import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
+import com.google.android.material.appbar.MaterialToolbar
+
+fun AppCompatActivity.applyToolbar(toolbar: MaterialToolbar) {
+ ToolbarContentTintHelper.colorBackButton(toolbar)
+ setSupportActionBar(toolbar)
+}
+
+inline fun Activity.extra(key: String, default: T? = null) = lazy {
+ val value = intent?.extras?.get(key)
+ if (value is T) value else default
+}
+
+inline fun Intent.extra(key: String, default: T? = null) = lazy {
+ val value = extras?.get(key)
+ if (value is T) value else default
+}
+
+inline fun Activity.extraNotNull(key: String, default: T? = null) = lazy {
+ val value = intent?.extras?.get(key)
+ requireNotNull(if (value is T) value else default) { key }
+}
+
+fun Activity.dip(@DimenRes id: Int): Int {
+ return resources.getDimensionPixelSize(id)
+}
+
+inline val Activity.rootView: View get() = findViewById(android.R.id.content).getChildAt(0)
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/extensions/ActivityThemeExtensions.kt b/app/src/main/java/code/name/monkey/retromusic/extensions/ActivityThemeExtensions.kt
new file mode 100644
index 000000000..d6c5a559d
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/extensions/ActivityThemeExtensions.kt
@@ -0,0 +1,235 @@
+package code.name.monkey.retromusic.extensions
+
+import android.app.ActivityManager
+import android.graphics.Color
+import android.os.Build
+import android.view.View
+import android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
+import android.view.WindowManager
+import android.view.inputmethod.InputMethodManager
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.getSystemService
+import androidx.core.view.*
+import androidx.fragment.app.FragmentActivity
+import code.name.monkey.appthemehelper.util.ColorUtil
+import code.name.monkey.appthemehelper.util.VersionUtils
+import code.name.monkey.retromusic.R
+import code.name.monkey.retromusic.util.PreferenceUtil
+
+fun AppCompatActivity.maybeSetScreenOn() {
+ if (PreferenceUtil.isScreenOnEnabled) {
+ window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+ } else {
+ window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+ }
+}
+
+fun AppCompatActivity.keepScreenOn(keepScreenOn: Boolean) {
+ if (keepScreenOn) {
+ window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+ } else {
+ window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+ }
+}
+
+fun AppCompatActivity.setEdgeToEdgeOrImmersive() {
+ if (PreferenceUtil.isFullScreenMode) {
+ setImmersiveFullscreen()
+ } else {
+ setDrawBehindSystemBars()
+ }
+}
+
+fun AppCompatActivity.setImmersiveFullscreen() {
+ if (PreferenceUtil.isFullScreenMode) {
+ WindowInsetsControllerCompat(window, window.decorView).apply {
+ systemBarsBehavior =
+ WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+ hide(WindowInsetsCompat.Type.systemBars())
+ }
+ if (VersionUtils.hasP()) {
+ window.attributes.layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
+ }
+ ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { _, insets ->
+ if (insets.displayCutout != null) {
+ insets
+ } else {
+ // Consume insets if display doesn't have a Cutout
+ WindowInsetsCompat.CONSUMED
+ }
+ }
+ }
+}
+
+fun AppCompatActivity.exitFullscreen() {
+ WindowInsetsControllerCompat(window, window.decorView).apply {
+ show(WindowInsetsCompat.Type.systemBars())
+ }
+}
+
+fun AppCompatActivity.hideStatusBar() {
+ hideStatusBar(PreferenceUtil.isFullScreenMode)
+}
+
+private fun AppCompatActivity.hideStatusBar(fullscreen: Boolean) {
+ val statusBar = window.decorView.rootView.findViewById