Here's a list of changes/features: https://github.com/RetroMusicPlayer/RetroMusicPlayer/releases/tag/v5.0

Internal Changes:
1) Migrated to ViewBinding
2) Migrated to Glide V4
3) Migrated to kotlin version of Material Dialogs
This commit is contained in:
Prathamesh More 2021-09-09 00:00:20 +05:30
parent fc42767031
commit bce6dbfa27
421 changed files with 13285 additions and 5757 deletions

View file

@ -1,31 +1,29 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: "androidx.navigation.safeargs.kotlin" apply plugin: "androidx.navigation.safeargs.kotlin"
apply plugin: 'kotlin-parcelize'
android { android {
compileSdkVersion 29 compileSdkVersion 31
buildToolsVersion = '29.0.3' buildToolsVersion = '29.0.3'
defaultConfig { defaultConfig {
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 29 targetSdkVersion 29
renderscriptTargetApi 29 //must match target sdk and build tools renderscriptTargetApi 29//must match target sdk and build tools
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
applicationId "code.name.monkey.retromusic" applicationId "code.name.monkey.retromusic"
versionCode 10503 versionCode 10519
versionName '4.0.010' + "_" + getDate() versionName '5.0.0' + "_" + getDate()
multiDexEnabled true
buildConfigField("String", "GOOGLE_PLAY_LICENSING_KEY", "\"${getProperty(getProperties('../public.properties'), 'GOOGLE_PLAY_LICENSE_KEY')}\"") buildConfigField("String", "GOOGLE_PLAY_LICENSING_KEY", "\"${getProperty(getProperties('../public.properties'), 'GOOGLE_PLAY_LICENSE_KEY')}\"")
} }
signingConfigs { signingConfigs {
release { release {
Properties properties = getProperties('/Users/apple/Documents/Github/music.jks') Properties properties = getProperties('retro.properties')
storeFile file(getProperty(properties, 'storeFile')) storeFile file(getProperty(properties, 'storeFile'))
keyAlias getProperty(properties, 'keyAlias') keyAlias getProperty(properties, 'keyAlias')
storePassword getProperty(properties, 'storePassword') storePassword getProperty(properties, 'storePassword')
@ -36,7 +34,6 @@ android {
release { release {
//debuggable true //debuggable true
minifyEnabled true minifyEnabled true
//shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release signingConfig signingConfigs.release
} }
@ -46,6 +43,10 @@ android {
} }
} }
buildFeatures{
viewBinding true
}
packagingOptions { packagingOptions {
exclude 'META-INF/LICENSE' exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE' exclude 'META-INF/NOTICE'
@ -67,17 +68,11 @@ android {
configurations.all { configurations.all {
resolutionStrategy.force 'com.google.code.findbugs:jsr305:1.3.9' resolutionStrategy.force 'com.google.code.findbugs:jsr305:1.3.9'
} }
androidExtensions {
experimental = true
}
kapt {
generateStubs = true
}
} }
def getProperties(String fileName) { def getProperties(String fileName) {
final Properties properties = new Properties() final Properties properties = new Properties()
def file = file(fileName) def file = rootProject.file(fileName)
if (file.exists()) { if (file.exists()) {
file.withInputStream { stream -> properties.load(stream) } file.withInputStream { stream -> properties.load(stream) }
} }
@ -95,76 +90,76 @@ static def getDate() {
dependencies { dependencies {
implementation project(':appthemehelper') implementation project(':appthemehelper')
implementation 'androidx.multidex:multidex:2.0.1'
implementation "androidx.gridlayout:gridlayout:1.0.0" implementation "androidx.gridlayout:gridlayout:1.0.0"
implementation "androidx.cardview:cardview:1.0.0" implementation "androidx.cardview:cardview:1.0.0"
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.annotation:annotation:1.1.0' implementation 'androidx.annotation:annotation:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.preference:preference-ktx:1.1.1' implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.fragment:fragment-ktx:1.2.5'
implementation 'androidx.palette:palette-ktx:1.0.0' implementation 'androidx.palette:palette-ktx:1.0.0'
def nav_version = "2.3.2" //Cast Dependencies
implementation 'androidx.mediarouter:mediarouter:1.2.5'
implementation 'com.google.android.gms:play-services-cast-framework:20.0.0'
//WebServer by NanoHttpd
implementation "org.nanohttpd:nanohttpd:2.3.1"
def nav_version = "2.4.0-alpha07"
implementation "androidx.navigation:navigation-runtime-ktx:$nav_version" implementation "androidx.navigation:navigation-runtime-ktx:$nav_version"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version" implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
def room_version = "2.2.5" def room_version = "2.3.0"
implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version" implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version" kapt "androidx.room:room-compiler:$room_version"
def lifecycle_version = "2.2.0" def lifecycle_version = "2.3.1"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
implementation 'com.google.android.play:core-ktx:1.8.1' implementation 'com.google.android.play:core-ktx:1.8.1'
implementation 'com.google.android.material:material:1.3.0-alpha04' implementation 'com.google.android.material:material:1.5.0-alpha03'
def retrofit_version = '2.9.0' def retrofit_version = '2.9.0'
implementation "com.squareup.retrofit2:retrofit:$retrofit_version" implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version" implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
implementation 'com.squareup.okhttp3:logging-interceptor:3.6.0' implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.2'
def material_dialog_version = "0.9.6.0" def material_dialog_version = "3.3.0"
implementation "com.afollestad.material-dialogs:core:$material_dialog_version" implementation "com.afollestad.material-dialogs:core:$material_dialog_version"
implementation "com.afollestad.material-dialogs:commons:$material_dialog_version" implementation "com.afollestad.material-dialogs:input:$material_dialog_version"
implementation "com.afollestad.material-dialogs:color:$material_dialog_version"
implementation "com.afollestad.material-dialogs:bottomsheets:$material_dialog_version"
//noinspection GradleDependency
implementation 'com.afollestad:material-cab:0.1.12' implementation 'com.afollestad:material-cab:0.1.12'
def kotlin_coroutines_version = "1.3.8" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10"
def kotlin_coroutines_version = "1.5.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
def koin_version = "2.1.5" def koin_version = "2.1.5"
implementation "org.koin:koin-core:$koin_version" implementation "org.koin:koin-core:$koin_version"
implementation "org.koin:koin-core-ext:$koin_version"
implementation "org.koin:koin-androidx-scope:$koin_version"
implementation "org.koin:koin-androidx-viewmodel:$koin_version" implementation "org.koin:koin-androidx-viewmodel:$koin_version"
implementation "org.koin:koin-androidx-fragment:$koin_version"
implementation "org.koin:koin-androidx-ext:$koin_version"
implementation 'com.github.bumptech.glide:glide:3.8.0' implementation 'com.github.bumptech.glide:glide:4.12.0'
implementation 'com.github.bumptech.glide:okhttp3-integration:1.5.0' kapt 'com.github.bumptech.glide:compiler:4.12.0'
implementation 'com.github.bumptech.glide:okhttp3-integration:4.12.0'
implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0' implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0'
implementation 'org.eclipse.mylyn.github:org.eclipse.egit.github.core:3.4.0.201406110918-r' implementation 'org.eclipse.mylyn.github:org.eclipse.egit.github.core:3.4.0.201406110918-r'
implementation 'com.github.ksoichiro:android-observablescrollview:1.6.0' implementation 'org.bitbucket.ijabz:jaudiotagger:2.2.5'
implementation 'com.github.kabouzeid:recyclerview-fastscroll:1.9-kmod'
implementation 'com.github.AdrienPoupa:jaudiotagger:2.2.3'
implementation 'com.anjlab.android.iab.v3:library:1.1.0' implementation 'com.anjlab.android.iab.v3:library:1.1.0'
implementation 'com.r0adkll:slidableactivity:2.1.0' implementation 'com.r0adkll:slidableactivity:2.1.0'
implementation 'com.heinrichreimersoftware:material-intro:1.6' implementation 'com.heinrichreimersoftware:material-intro:2.0.0'
implementation 'com.github.dhaval2404:imagepicker:1.7.1' implementation 'com.github.dhaval2404:imagepicker:1.7.1'
implementation 'org.jsoup:jsoup:1.11.1' implementation 'me.zhanghai.android.fastscroll:library:1.1.7'
implementation 'me.zhanghai.android.fastscroll:library:1.1.0'
implementation 'me.jorgecastillo:androidcolorx:0.2.0'
implementation 'org.jsoup:jsoup:1.11.1'
debugImplementation 'com.amitshekhar.android:debug-db:1.0.6' debugImplementation 'com.amitshekhar.android:debug-db:1.0.6'
} }

View file

@ -23,6 +23,9 @@
# If you keep the line number information, uncomment this to # If you keep the line number information, uncomment this to
# hide the original source file name. # hide the original source file name.
#-renamesourcefileattribute SourceFile #-renamesourcefileattribute SourceFile
-keepnames class **
-keepnames class ** { *; }
-keepattributes SourceFile,LineNumberTable
-dontwarn java.lang.invoke.* -dontwarn java.lang.invoke.*
-dontwarn **$$Lambda$* -dontwarn **$$Lambda$*
@ -34,7 +37,8 @@
# Glide # Glide
-keep public class * implements com.bumptech.glide.module.GlideModule -keep public class * implements com.bumptech.glide.module.GlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { -keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES; **[] $VALUES;
public *; public *;
} }
@ -47,8 +51,6 @@
#-dontwarn #-dontwarn
#-ignorewarnings #-ignorewarnings
-dontshrink
-dontobfuscate
-dontwarn org.jaudiotagger.** -dontwarn org.jaudiotagger.**
-keep class org.jaudiotagger.** { *; } -keep class org.jaudiotagger.** { *; }
@ -60,3 +62,7 @@
-keep class * extends androidx.fragment.app.Fragment{} -keep class * extends androidx.fragment.app.Fragment{}
-keepnames class * extends android.os.Parcelable -keepnames class * extends android.os.Parcelable
-keepnames class * extends java.io.Serializable -keepnames class * extends java.io.Serializable
-keep class code.name.monkey.retromusic.network.model.** { *; }
-keep class code.name.monkey.retromusic.model.CategoryInfo { *; }
-keep class com.google.android.material.bottomsheet.** { *; }
-keep class code.name.monkey.retromusic.Constants { *; }

View file

@ -6,6 +6,11 @@
<item name="fontFamily">@font/sans</item> <item name="fontFamily">@font/sans</item>
</style> </style>
<style name="TextViewNormalCompress" parent="TextAppearance.MaterialComponents.Caption">
<item name="android:textSize">14sp</item>
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewHeadline4" parent="TextAppearance.MaterialComponents.Headline4"> <style name="TextViewHeadline4" parent="TextAppearance.MaterialComponents.Headline4">
<item name="fontFamily">@font/sans</item> <item name="fontFamily">@font/sans</item>
</style> </style>
@ -91,6 +96,11 @@
<style name="circleImageView" parent=""> <style name="circleImageView" parent="">
<item name="cornerFamily">rounded</item> <item name="cornerFamily">rounded</item>
<item name="cornerSize">5%</item> <item name="cornerSize">16dp</item>
</style>
<style name="BottomSheetItemTextAppearance" parent="Widget.MaterialComponents.BottomNavigationView.Colored">
<item name="android:textSize">13sp</item>
<item name="fontFamily">@font/sans</item>
</style> </style>
</resources> </resources>

View file

@ -5,9 +5,10 @@
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" /> <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" /> <uses-permission android:name="android.permission.BROADCAST_STICKY" />
@ -29,7 +30,7 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.RetroMusic.FollowSystem" android:theme="@style/Theme.RetroMusic.FollowSystem"
android:usesCleartextTraffic="false" android:usesCleartextTraffic="true"
tools:ignore="AllowBackup,GoogleAppIndexingWarning" tools:ignore="AllowBackup,GoogleAppIndexingWarning"
tools:targetApi="m"> tools:targetApi="m">
<activity <activity
@ -131,6 +132,20 @@
android:name=".activities.saf.SAFGuideActivity" android:name=".activities.saf.SAFGuideActivity"
android:theme="@style/Theme.Intro" /> android:theme="@style/Theme.Intro" />
<activity
android:name=".cast.ExpandedControlsActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/Theme.AppCompat.NoActionBar"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.MainActivity"/>
</activity>
<provider <provider
android:name=".misc.GenericFileProvider" android:name=".misc.GenericFileProvider"
android:authorities="${applicationId}.provider" android:authorities="${applicationId}.provider"
@ -246,16 +261,24 @@
android:name="com.lge.support.SPLIT_WINDOW" android:name="com.lge.support.SPLIT_WINDOW"
android:value="true" /> android:value="true" />
<meta-data
android:name="code.name.monkey.retromusic.glide.RetroMusicGlideModule"
android:value="GlideModule" />
<meta-data
android:name="com.bumptech.glide.integration.okhttp3.OkHttpGlideModule"
android:value="GlideModule" />
<meta-data <meta-data
android:name="com.android.vending.splits.required" android:name="com.android.vending.splits.required"
android:value="true" /> android:value="true" />
<!-- Android Auto -->
<!-- <meta-data
android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc"/>
<meta-data
android:name="com.google.android.gms.car.application.theme"
android:resource="@style/CarTheme" />
<meta-data
android:name="com.google.android.gms.car.notification.SmallIcon"
android:resource="@drawable/ic_notification"/>
-->
<!-- ChromeCast -->
<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="code.name.monkey.retromusic.cast.CastOptionsProvider" />
</application> </application>
</manifest> </manifest>

View file

@ -6,14 +6,14 @@
"image": "https://i.imgur.com/AoVs9oj.jpg" "image": "https://i.imgur.com/AoVs9oj.jpg"
}, },
{ {
"name": "Lennart Glamann", "name": "Prathamesh More",
"summary": "Play Store Banner & Images", "summary": "Developer",
"link": "https://t.me/FlixbusLennart", "link": "https://prathameshmm02.github.io",
"image": "https://i.imgur.com/Q5Nsx1R.jpg" "image": "https://i.imgur.com/ZHoOrHx.jpg"
}, },
{ {
"name": "Daksh P. Jain", "name": "Daksh P. Jain",
"summary": "Support Representative & Moderator", "summary": "Website & GitHub Maintainer",
"link": "https://daksh.eu.org", "link": "https://daksh.eu.org",
"image": "https://i.imgur.com/fnYpg65.jpg" "image": "https://i.imgur.com/fnYpg65.jpg"
}, },
@ -23,9 +23,15 @@
"link": "https://t.me/MilindGoel15", "link": "https://t.me/MilindGoel15",
"image": "https://i.imgur.com/Bz4De21_d.jpg" "image": "https://i.imgur.com/Bz4De21_d.jpg"
}, },
{ {
"name": "Lennart Glamann",
"summary": "Play Store Banner & Images",
"link": "https://t.me/FlixbusLennart",
"image": "https://i.imgur.com/Q5Nsx1R.jpg"
},
{
"name": "Haythem Gataa", "name": "Haythem Gataa",
"summary": "App Logo Designer", "summary": "App Logo & Banners",
"link": "https://dribbble.com/haythemgataa", "link": "https://dribbble.com/haythemgataa",
"image": "https://i.imgur.com/g5RuIZq.jpg" "image": "https://i.imgur.com/g5RuIZq.jpg"
} }

View file

@ -6,9 +6,11 @@
word-wrap: break-word; word-wrap: break-word;
} }
body { div{
padding-left: 1rem; margin: 20px 10px;
padding-right: 1rem; padding: 10px;
border-radius: 10px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
} }
h2 { h2 {
@ -18,10 +20,7 @@
li { li {
font-size: 0.85rem; font-size: 0.85rem;
padding-top: 0.5rem; padding: 0.5rem 0;
padding-left: 0;
padding-right: 0;
color: rgba(0, 0, 0, 0.8);
} }
ul { ul {
@ -44,39 +43,62 @@
margin-block-end: 0.5rem; margin-block-end: 0.5rem;
} }
h3 span { h3 {
border-radius: 0.2rem; margin: 10px 0px;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.3rem;
padding-bottom: 0.3rem;
font-size: 1rem; font-size: 1rem;
} }
{style-placeholder} {style-placeholder}
</style> </style>
</head> </head>
<body> <body>
<h5>April 30, 2020</h5> <h2>
<h2>v3.5.110</h2> <b>Clear the app if it crashes after updating</b>
<span class="tag"><i>Beta version</i></span> </h2>
<h3><span class="colorHeader">What's New</span></h3> <div>
<ul>
<li>Changed profile form image to icon</li> <h5>September 06, 2021</h5>
<li>New what's new screen</li> <h2>v5.0.0</h2>
<li>Added In-App language changer, where you can select language</li> <h3>What's New</h3>
</ul> <ul>
<h3><span class="colorHeader">Improved</span></h3> <li>Added Chromecast support</li>
<ul> <li>Added animated icons</li>
<li>Improved loading of Songs, Albums, Artists, Genres, Playlists</li> <li>Added cross-fade (experimental)</li>
</ul> <li>Added ability to remember the last tab</li>
<li>Added whitelisting songs</li>
<li>Added support for embedded synced lyrics</li>
<li>Added lyrics editor for normal and synced lyrics</li>
<li>Added playlist ordering</li>
<li>Added search filters</li>
<li>Added audio fade</li>
<li>Added Multi-select in album and artist details</li>
<li>Added SD card from folders tab</li>
<li>Added Synced lyrics in all themes</li>
<li>Added Swipe anywhere to change the song</li>
<li>Added album artist</li>
<li> Albums now show album artists instead of artists of the first song</li>
</ul>
<h3>Fixed</h3>
<ul>
<li> Fixed playlist preview images</li>
<li> Fixed language switching</li>
</ul>
<h3>Improved</h3>
<ul>
<li>Improved playlists tab</li>
<li> Improved genres tab</li>
</ul>
</div>
<!--<h3><span class="colorHeader">Bug fixes</span></h3> <!--<h3><span class="colorHeader">Bug fixes</span></h3>
<ul> <ul>
<li></li> <li></li>
</ul>--> </ul>-->
<p>*If you face any UI related issues you clear app data and cache, if itsnot working try to
uninstall and install
again. </p>
</body> </body>

View file

@ -14,8 +14,8 @@
*/ */
package code.name.monkey.retromusic package code.name.monkey.retromusic
import android.app.Application
import android.widget.Toast import android.widget.Toast
import androidx.multidex.MultiDexApplication
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.VersionUtils import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.Constants.PRO_VERSION_PRODUCT_ID import code.name.monkey.retromusic.Constants.PRO_VERSION_PRODUCT_ID
@ -25,7 +25,7 @@ import com.anjlab.android.iab.v3.TransactionDetails
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin import org.koin.core.context.startKoin
class App : MultiDexApplication() { class App : Application() {
lateinit var billingProcessor: BillingProcessor lateinit var billingProcessor: BillingProcessor

View file

@ -21,7 +21,8 @@ object Constants {
const val PRO_VERSION_PRODUCT_ID = "pro_version" const val PRO_VERSION_PRODUCT_ID = "pro_version"
const val RATE_ON_GOOGLE_PLAY = const val RATE_ON_GOOGLE_PLAY =
"https://play.google.com/store/apps/details?id=code.name.monkey.retromusic" "https://play.google.com/store/apps/details?id=code.name.monkey.retromusic"
const val TRANSLATE = "https://github.com/RetroMusicPlayer/RetroMusicPlayer" const val TRANSLATE = "https://crowdin.com/project/retromusicplayer"
const val WEBSITE = "https://retromusic.app"
const val GITHUB_PROJECT = "https://github.com/RetroMusicPlayer/RetroMusicPlayer" const val GITHUB_PROJECT = "https://github.com/RetroMusicPlayer/RetroMusicPlayer"
const val TELEGRAM_CHANGE_LOG = "https://t.me/retromusiclog" const val TELEGRAM_CHANGE_LOG = "https://t.me/retromusiclog"
const val USER_PROFILE = "profile.jpg" const val USER_PROFILE = "profile.jpg"
@ -49,7 +50,7 @@ object Constants {
MediaStore.Audio.AudioColumns.ARTIST_ID, // 9 MediaStore.Audio.AudioColumns.ARTIST_ID, // 9
MediaStore.Audio.AudioColumns.ARTIST, // 10 MediaStore.Audio.AudioColumns.ARTIST, // 10
MediaStore.Audio.AudioColumns.COMPOSER, // 11 MediaStore.Audio.AudioColumns.COMPOSER, // 11
"album_artist" // 12 ALBUM_ARTIST // 12
) )
const val NUMBER_OF_TOP_TRACKS = 99 const val NUMBER_OF_TOP_TRACKS = 99
} }
@ -66,7 +67,7 @@ const val EXTRA_SONG_INFO = "extra_song_info"
const val DESATURATED_COLOR = "desaturated_color" const val DESATURATED_COLOR = "desaturated_color"
const val BLACK_THEME = "black_theme" const val BLACK_THEME = "black_theme"
const val KEEP_SCREEN_ON = "keep_screen_on" const val KEEP_SCREEN_ON = "keep_screen_on"
const val TYPE_HOME_BANNER = "type_home_banner" const val TOGGLE_HOME_BANNER = "toggle_home_banner"
const val NOW_PLAYING_SCREEN_ID = "now_playing_screen_id" const val NOW_PLAYING_SCREEN_ID = "now_playing_screen_id"
const val CAROUSEL_EFFECT = "carousel_effect" const val CAROUSEL_EFFECT = "carousel_effect"
const val COLORED_NOTIFICATION = "colored_notification" const val COLORED_NOTIFICATION = "colored_notification"
@ -129,6 +130,7 @@ const val START_DIRECTORY = "start_directory"
const val RECENTLY_PLAYED_CUTOFF = "recently_played_interval" const val RECENTLY_PLAYED_CUTOFF = "recently_played_interval"
const val LOCK_SCREEN = "lock_screen" const val LOCK_SCREEN = "lock_screen"
const val ALBUM_ARTISTS_ONLY = "album_artists_only" 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 ALBUM_DETAIL_SONG_SORT_ORDER = "album_detail_song_sort_order"
const val LYRICS_OPTIONS = "lyrics_tab_position" const val LYRICS_OPTIONS = "lyrics_tab_position"
const val CHOOSE_EQUALIZER = "choose_equalizer" const val CHOOSE_EQUALIZER = "choose_equalizer"
@ -138,3 +140,11 @@ const val SONG_GRID_STYLE = "song_grid_style"
const val PAUSE_ON_ZERO_VOLUME = "pause_on_zero_volume" const val PAUSE_ON_ZERO_VOLUME = "pause_on_zero_volume"
const val FILTER_SONG = "filter_song" const val FILTER_SONG = "filter_song"
const val EXPAND_NOW_PLAYING_PANEL = "expand_now_playing_panel" 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"

View file

@ -5,15 +5,20 @@ import android.content.ContextWrapper;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.LocaleList; import android.os.LocaleList;
import code.name.monkey.appthemehelper.util.VersionUtils;
import com.google.android.gms.common.annotation.KeepName;
import java.util.Locale; import java.util.Locale;
import code.name.monkey.appthemehelper.util.VersionUtils;
public class LanguageContextWrapper extends ContextWrapper { public class LanguageContextWrapper extends ContextWrapper {
public LanguageContextWrapper(Context base) { public LanguageContextWrapper(Context base) {
super(base); super(base);
} }
@KeepName
public static LanguageContextWrapper wrap(Context context, Locale newLocale) { public static LanguageContextWrapper wrap(Context context, Locale newLocale) {
Resources res = context.getResources(); Resources res = context.getResources();
Configuration configuration = res.getConfiguration(); Configuration configuration = res.getConfiguration();

View file

@ -3,6 +3,7 @@ package code.name.monkey.retromusic
import androidx.room.Room import androidx.room.Room
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteDatabase
import code.name.monkey.retromusic.auto.AutoMusicProvider
import code.name.monkey.retromusic.db.BlackListStoreDao import code.name.monkey.retromusic.db.BlackListStoreDao
import code.name.monkey.retromusic.db.BlackListStoreEntity import code.name.monkey.retromusic.db.BlackListStoreEntity
import code.name.monkey.retromusic.db.PlaylistWithSongs import code.name.monkey.retromusic.db.PlaylistWithSongs
@ -85,6 +86,18 @@ private val roomModule = module {
RealRoomRepository(get(), get(), get(), get(), get()) RealRoomRepository(get(), get(), get(), get(), get())
} bind RoomRepository::class } bind RoomRepository::class
} }
private val autoModule = module {
single {
AutoMusicProvider(androidContext(),
get(),
get(),
get(),
get(),
get(),
get()
)
}
}
private val mainModule = module { private val mainModule = module {
single { single {
androidContext().contentResolver androidContext().contentResolver
@ -167,10 +180,11 @@ private val viewModules = module {
) )
} }
viewModel { (artistId: Long) -> viewModel { (artistId: Long?, artistName: String?) ->
ArtistDetailsViewModel( ArtistDetailsViewModel(
get(), get(),
artistId artistId,
artistName
) )
} }
@ -189,4 +203,4 @@ private val viewModules = module {
} }
} }
val appModules = listOf(mainModule, dataModule, viewModules, networkModule, roomModule) val appModules = listOf(mainModule, dataModule, autoModule, viewModules, networkModule, roomModule)

View file

@ -4,8 +4,11 @@ import android.content.Context;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.coordinatorlayout.widget.CoordinatorLayout;
import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.bottomsheet.BottomSheetBehavior;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
public class RetroBottomSheetBehavior<V extends View> extends BottomSheetBehavior<V> { public class RetroBottomSheetBehavior<V extends View> extends BottomSheetBehavior<V> {

View file

@ -23,10 +23,12 @@ import android.widget.SeekBar
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.databinding.ActivityDriveModeBinding
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.glide.BlurTransformation import code.name.monkey.retromusic.glide.BlurTransformation
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper.Callback import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper.Callback
@ -35,19 +37,19 @@ import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.activity_drive_mode.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
/** /**
* Created by hemanths on 2020-02-02. * Created by hemanths on 2020-02-02.
*/ */
class DriveModeActivity : AbsMusicServiceActivity(), Callback { class DriveModeActivity : AbsMusicServiceActivity(), Callback {
private lateinit var binding: ActivityDriveModeBinding
private var lastPlaybackControlsColor: Int = Color.GRAY private var lastPlaybackControlsColor: Int = Color.GRAY
private var lastDisabledPlaybackControlsColor: Int = Color.GRAY private var lastDisabledPlaybackControlsColor: Int = Color.GRAY
private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper
@ -55,12 +57,13 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar() setDrawUnderStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_drive_mode) binding = ActivityDriveModeBinding.inflate(layoutInflater)
setContentView(binding.root)
setUpMusicControllers() setUpMusicControllers()
progressViewUpdateHelper = MusicProgressViewUpdateHelper(this) progressViewUpdateHelper = MusicProgressViewUpdateHelper(this)
lastPlaybackControlsColor = ThemeStore.accentColor(this) lastPlaybackControlsColor = ThemeStore.accentColor(this)
close.setOnClickListener { binding.close.setOnClickListener {
onBackPressed() onBackPressed()
} }
} }
@ -75,7 +78,7 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
} }
private fun setupFavouriteToggle() { private fun setupFavouriteToggle() {
songFavourite.setOnClickListener { binding.songFavourite.setOnClickListener {
MusicUtil.toggleFavorite( MusicUtil.toggleFavorite(
this@DriveModeActivity, this@DriveModeActivity,
MusicPlayerRemote.currentSong MusicPlayerRemote.currentSong
@ -88,13 +91,13 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
val isFavourite = val isFavourite =
MusicUtil.isFavorite(this@DriveModeActivity, MusicPlayerRemote.currentSong) MusicUtil.isFavorite(this@DriveModeActivity, MusicPlayerRemote.currentSong)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
songFavourite.setImageResource(if (isFavourite) R.drawable.ic_favorite else R.drawable.ic_favorite_border) binding.songFavourite.setImageResource(if (isFavourite) R.drawable.ic_favorite else R.drawable.ic_favorite_border)
} }
} }
} }
private fun setUpProgressSlider() { private fun setUpProgressSlider() {
progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { binding.progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (fromUser) { if (fromUser) {
MusicPlayerRemote.seekTo(progress) MusicPlayerRemote.seekTo(progress)
@ -119,20 +122,20 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
private fun setUpPrevNext() { private fun setUpPrevNext() {
nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } binding.nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() }
previousButton.setOnClickListener { MusicPlayerRemote.back() } binding.previousButton.setOnClickListener { MusicPlayerRemote.back() }
} }
private fun setUpShuffleButton() { private fun setUpShuffleButton() {
shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } binding.shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() }
} }
private fun setUpRepeatButton() { private fun setUpRepeatButton() {
repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } binding.repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() }
} }
private fun setUpPlayPauseFab() { private fun setUpPlayPauseFab() {
playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) binding.playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler())
} }
override fun onRepeatModeChanged() { override fun onRepeatModeChanged() {
@ -161,19 +164,19 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
private fun updatePlayPauseDrawableState() { private fun updatePlayPauseDrawableState() {
if (MusicPlayerRemote.isPlaying) { if (MusicPlayerRemote.isPlaying) {
playPauseButton.setImageResource(R.drawable.ic_pause) binding.playPauseButton.setImageResource(R.drawable.ic_pause)
} else { } else {
playPauseButton.setImageResource(R.drawable.ic_play_arrow) binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow)
} }
} }
fun updateShuffleState() { fun updateShuffleState() {
when (MusicPlayerRemote.shuffleMode) { when (MusicPlayerRemote.shuffleMode) {
MusicService.SHUFFLE_MODE_SHUFFLE -> shuffleButton.setColorFilter( MusicService.SHUFFLE_MODE_SHUFFLE -> binding.shuffleButton.setColorFilter(
lastPlaybackControlsColor, lastPlaybackControlsColor,
PorterDuff.Mode.SRC_IN PorterDuff.Mode.SRC_IN
) )
else -> shuffleButton.setColorFilter( else -> binding.shuffleButton.setColorFilter(
lastDisabledPlaybackControlsColor, lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN PorterDuff.Mode.SRC_IN
) )
@ -183,19 +186,25 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
private fun updateRepeatState() { private fun updateRepeatState() {
when (MusicPlayerRemote.repeatMode) { when (MusicPlayerRemote.repeatMode) {
MusicService.REPEAT_MODE_NONE -> { MusicService.REPEAT_MODE_NONE -> {
repeatButton.setImageResource(R.drawable.ic_repeat) binding.repeatButton.setImageResource(R.drawable.ic_repeat)
repeatButton.setColorFilter( binding.repeatButton.setColorFilter(
lastDisabledPlaybackControlsColor, lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN PorterDuff.Mode.SRC_IN
) )
} }
MusicService.REPEAT_MODE_ALL -> { MusicService.REPEAT_MODE_ALL -> {
repeatButton.setImageResource(R.drawable.ic_repeat) binding.repeatButton.setImageResource(R.drawable.ic_repeat)
repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) binding.repeatButton.setColorFilter(
lastPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
} }
MusicService.REPEAT_MODE_THIS -> { MusicService.REPEAT_MODE_THIS -> {
repeatButton.setImageResource(R.drawable.ic_repeat_one) binding.repeatButton.setImageResource(R.drawable.ic_repeat_one)
repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) binding.repeatButton.setColorFilter(
lastPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
} }
} }
} }
@ -209,29 +218,29 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
private fun updateSong() { private fun updateSong() {
val song = MusicPlayerRemote.currentSong val song = MusicPlayerRemote.currentSong
songTitle.text = song.title binding.songTitle.text = song.title
songText.text = song.artistName binding.songText.text = song.artistName
SongGlideRequest.Builder.from(Glide.with(this), song) GlideApp.with(this)
.checkIgnoreMediaStore(this) .asBitmapPalette()
.generatePalette(this) .songCoverOptions(song)
.build() .load(RetroGlideExtension.getSongModel(song))
.transform(BlurTransformation.Builder(this).build()) .transform(BlurTransformation.Builder(this).build())
.into(object : RetroMusicColoredTarget(image) { .into(object : RetroMusicColoredTarget(binding.image) {
override fun onColorReady(colors: MediaNotificationProcessor) { override fun onColorReady(colors: MediaNotificationProcessor) {
} }
}) })
} }
override fun onUpdateProgressViews(progress: Int, total: Int) { override fun onUpdateProgressViews(progress: Int, total: Int) {
progressSlider.max = total binding.progressSlider.max = total
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) val animator = ObjectAnimator.ofInt(binding.progressSlider, "progress", progress)
animator.duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME animator.duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME
animator.interpolator = LinearInterpolator() animator.interpolator = LinearInterpolator()
animator.start() animator.start()
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
} }
} }

View file

@ -18,19 +18,23 @@ import android.graphics.Color;
import android.os.Bundle; import android.os.Bundle;
import android.view.MenuItem; import android.view.MenuItem;
import android.webkit.WebView; import android.webkit.WebView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import code.name.monkey.appthemehelper.ThemeStore; import code.name.monkey.appthemehelper.ThemeStore;
import code.name.monkey.appthemehelper.util.ATHUtil; import code.name.monkey.appthemehelper.util.ATHUtil;
import code.name.monkey.appthemehelper.util.ColorUtil; import code.name.monkey.appthemehelper.util.ColorUtil;
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper;
import code.name.monkey.retromusic.R; import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.activities.base.AbsBaseActivity; import code.name.monkey.retromusic.activities.base.AbsBaseActivity;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import org.jetbrains.annotations.Nullable;
/** Created by hemanths on 2019-09-27. */ /** Created by hemanths on 2019-09-27. */
public class LicenseActivity extends AbsBaseActivity { public class LicenseActivity extends AbsBaseActivity {

View file

@ -23,27 +23,29 @@ import android.view.WindowManager
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.databinding.ActivityLockScreenBinding
import code.name.monkey.retromusic.extensions.whichFragment import code.name.monkey.retromusic.extensions.whichFragment
import code.name.monkey.retromusic.fragments.player.lockscreen.LockScreenControlsFragment import code.name.monkey.retromusic.fragments.player.lockscreen.LockScreenControlsFragment
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
import com.r0adkll.slidr.Slidr import com.r0adkll.slidr.Slidr
import com.r0adkll.slidr.model.SlidrConfig import com.r0adkll.slidr.model.SlidrConfig
import com.r0adkll.slidr.model.SlidrListener import com.r0adkll.slidr.model.SlidrListener
import com.r0adkll.slidr.model.SlidrPosition import com.r0adkll.slidr.model.SlidrPosition
import kotlinx.android.synthetic.main.activity_lock_screen.*
class LockScreenActivity : AbsMusicServiceActivity() { class LockScreenActivity : AbsMusicServiceActivity() {
private lateinit var binding: ActivityLockScreenBinding
private var fragment: LockScreenControlsFragment? = null private var fragment: LockScreenControlsFragment? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar() setDrawUnderStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
lockScreenInit() lockScreenInit()
setContentView(R.layout.activity_lock_screen) binding = ActivityLockScreenBinding.inflate(layoutInflater)
setContentView(binding.root)
hideStatusBar() hideStatusBar()
setStatusbarColorAuto() setStatusbarColorAuto()
setNavigationbarColorAuto() setNavigationbarColorAuto()
@ -107,9 +109,12 @@ class LockScreenActivity : AbsMusicServiceActivity() {
private fun updateSongs() { private fun updateSongs() {
val song = MusicPlayerRemote.currentSong val song = MusicPlayerRemote.currentSong
SongGlideRequest.Builder.from(Glide.with(this), song).checkIgnoreMediaStore(this) GlideApp.with(this)
.generatePalette(this).build().dontAnimate() .asBitmapPalette()
.into(object : RetroMusicColoredTarget(image) { .songCoverOptions(song)
.load(RetroGlideExtension.getSongModel(song))
.dontAnimate()
.into(object : RetroMusicColoredTarget(binding.image) {
override fun onColorReady(colors: MediaNotificationProcessor) { override fun onColorReady(colors: MediaNotificationProcessor) {
fragment?.setColor(colors) fragment?.setColor(colors)
} }

View file

@ -15,38 +15,64 @@
package code.name.monkey.retromusic.activities package code.name.monkey.retromusic.activities
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.text.InputType
import android.view.MenuItem import android.view.*
import android.view.WindowManager
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.interpolator.view.animation.FastOutSlowInInterpolator import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.activities.tageditor.WriteTagsAsyncTask
import code.name.monkey.retromusic.databinding.ActivityLyricsBinding
import code.name.monkey.retromusic.databinding.FragmentNormalLyricsBinding
import code.name.monkey.retromusic.databinding.FragmentSyncedLyricsBinding
import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.surfaceColor import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.extensions.textColorSecondary
import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.lyrics.LrcView import code.name.monkey.retromusic.lyrics.LrcView
import code.name.monkey.retromusic.model.LoadingInfo
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.LyricUtil import code.name.monkey.retromusic.util.LyricUtil
import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil
import com.afollestad.materialdialogs.LayoutMode
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.bottomsheets.BottomSheet
import com.afollestad.materialdialogs.input.input
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import com.google.android.material.transition.platform.MaterialArcMotion import com.google.android.material.tabs.TabLayoutMediator
import com.google.android.material.transition.platform.MaterialContainerTransform import com.google.android.material.transition.platform.MaterialContainerTransform
import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback import kotlinx.coroutines.*
import kotlinx.android.synthetic.main.activity_lyrics.* import org.jaudiotagger.audio.AudioFileIO
import org.jaudiotagger.tag.FieldKey
import java.io.File
import java.util.*
class LyricsActivity : AbsMusicServiceActivity(), MusicProgressViewUpdateHelper.Callback { class LyricsActivity : AbsMusicServiceActivity() {
private lateinit var updateHelper: MusicProgressViewUpdateHelper
private lateinit var binding: ActivityLyricsBinding
private lateinit var song: Song private lateinit var song: Song
private val lyricsSectionsAdapter = LyricsSectionsAdapter(this)
private val googleSearchLrcUrl: String private val googleSearchLrcUrl: String
get() { get() {
var baseUrl = "http://www.google.com/search?" var baseUrl = "http://www.google.com/search?"
var query = song.title + "+" + song.artistName var query = song.title + "+" + song.artistName
query = "q=" + query.replace(" ", "+") + " .lrc" query = "q=" + query.replace(" ", "+") + " lyrics"
baseUrl += query
return baseUrl
}
private val syairSearchLrcUrl: String
get() {
var baseUrl = "https://www.syair.info/search?"
var query = song.title + "+" + song.artistName
query = "q=" + query.replace(" ", "+")
baseUrl += query baseUrl += query
return baseUrl return baseUrl
} }
@ -63,75 +89,55 @@ class LyricsActivity : AbsMusicServiceActivity(), MusicProgressViewUpdateHelper.
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_lyrics) binding = ActivityLyricsBinding.inflate(layoutInflater)
ViewCompat.setTransitionName(container, "lyrics") setContentView(binding.root)
ViewCompat.setTransitionName(binding.container, "lyrics")
setStatusbarColorAuto() setStatusbarColorAuto()
setTaskDescriptionColorAuto() setTaskDescriptionColorAuto()
setNavigationbarColorAuto() setNavigationbarColorAuto()
setupWakelock() setupWakelock()
toolbar.setBackgroundColor(surfaceColor()) binding.toolbar.setBackgroundColor(surfaceColor())
ToolbarContentTintHelper.colorBackButton(toolbar) binding.tabLyrics.setBackgroundColor(surfaceColor())
setSupportActionBar(toolbar) binding.container.setBackgroundColor(surfaceColor())
ToolbarContentTintHelper.colorBackButton(binding.toolbar)
setSupportActionBar(binding.toolbar)
setupViews()
updateHelper = MusicProgressViewUpdateHelper(this, 500, 1000)
setupLyricsView()
} }
private fun setupLyricsView() {
lyricsView.apply { private fun setupViews() {
setCurrentColor(ThemeStore.accentColor(context)) binding.lyricsPager.adapter = lyricsSectionsAdapter
setTimeTextColor(ThemeStore.accentColor(context)) TabLayoutMediator(binding.tabLyrics, binding.lyricsPager) { tab, position ->
setTimelineColor(ThemeStore.accentColor(context)) tab.text = when (position) {
setTimelineTextColor(ThemeStore.accentColor(context)) 0 -> "Synced Lyrics"
setDraggable(true, LrcView.OnPlayClickListener { 1 -> "Normal Lyrics"
MusicPlayerRemote.seekTo(it.toInt()) else -> ""
return@OnPlayClickListener true
})
} }
}.attach()
// lyricsPager.isUserInputEnabled = false
binding.tabLyrics.setSelectedTabIndicatorColor(ThemeStore.accentColor(this))
binding.tabLyrics.setTabTextColors(textColorSecondary(), accentColor())
} }
override fun onResume() {
super.onResume()
updateHelper.start()
}
override fun onPause() {
super.onPause()
updateHelper.stop()
}
override fun onUpdateProgressViews(progress: Int, total: Int) {
lyricsView.updateTime(progress.toLong())
}
private fun loadLRCLyrics() {
lyricsView.setLabel("Empty")
val song = MusicPlayerRemote.currentSong
if (LyricUtil.isLrcOriginalFileExist(song.data)) {
lyricsView.loadLrc(LyricUtil.getLocalLyricOriginalFile(song.data))
} else if (LyricUtil.isLrcFileExist(song.title, song.artistName)) {
lyricsView.loadLrc(LyricUtil.getLocalLyricFile(song.title, song.artistName))
}
}
override fun onPlayingMetaChanged() { override fun onPlayingMetaChanged() {
super.onPlayingMetaChanged() super.onPlayingMetaChanged()
updateTitleSong() updateTitleSong()
loadLRCLyrics()
} }
override fun onServiceConnected() { override fun onServiceConnected() {
super.onServiceConnected() super.onServiceConnected()
updateTitleSong() updateTitleSong()
loadLRCLyrics()
} }
private fun updateTitleSong() { private fun updateTitleSong() {
song = MusicPlayerRemote.currentSong song = MusicPlayerRemote.currentSong
toolbar.title = song.title binding.toolbar.title = song.title
toolbar.subtitle = song.artistName binding.toolbar.subtitle = song.artistName
} }
private fun setupWakelock() { private fun setupWakelock() {
@ -149,8 +155,206 @@ class LyricsActivity : AbsMusicServiceActivity(), MusicProgressViewUpdateHelper.
return true return true
} }
if (item.itemId == R.id.action_search) { if (item.itemId == R.id.action_search) {
RetroUtil.openUrl(this, googleSearchLrcUrl) RetroUtil.openUrl(
this, when (binding.lyricsPager.currentItem) {
0 -> syairSearchLrcUrl
1 -> googleSearchLrcUrl
else -> googleSearchLrcUrl
}
)
} else if (item.itemId == R.id.action_edit) {
when (binding.lyricsPager.currentItem) {
0 -> {
editSyncedLyrics()
}
1 -> {
editNormalLyrics()
}
}
} }
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
private fun editNormalLyrics() {
var content = ""
val file = File(MusicPlayerRemote.currentSong.data)
try {
content = AudioFileIO.read(file).tagOrCreateDefault.getFirst(FieldKey.LYRICS)
} catch (e: Exception) {
e.printStackTrace()
}
MaterialDialog(this, BottomSheet(LayoutMode.WRAP_CONTENT)).show {
title(res = R.string.edit_normal_lyrics)
input(
hintRes = R.string.paste_lyrics_here,
prefill = content,
inputType = InputType.TYPE_TEXT_FLAG_MULTI_LINE or InputType.TYPE_CLASS_TEXT
) { _, input ->
val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java)
fieldKeyValueMap[FieldKey.LYRICS] = input.toString()
WriteTagsAsyncTask(this@LyricsActivity).execute(
LoadingInfo(
listOf(song.data), fieldKeyValueMap, null
)
)
}
positiveButton(res = R.string.save) {
(lyricsSectionsAdapter.fragments[1].first as NormalLyrics).loadNormalLyrics()
}
negativeButton(res = android.R.string.cancel)
}
}
private fun editSyncedLyrics() {
var lrcFile: File? = null
if (LyricUtil.isLrcOriginalFileExist(song.data)) {
lrcFile = LyricUtil.getLocalLyricOriginalFile(song.data)
} else if (LyricUtil.isLrcFileExist(song.title, song.artistName)) {
lrcFile = LyricUtil.getLocalLyricFile(song.title, song.artistName)
}
val content: String = LyricUtil.getStringFromLrc(lrcFile)
MaterialDialog(this, BottomSheet(LayoutMode.WRAP_CONTENT)).show {
title(res = R.string.edit_synced_lyrics)
input(
hintRes = R.string.paste_timeframe_lyrics_here,
prefill = content,
inputType = InputType.TYPE_TEXT_FLAG_MULTI_LINE or InputType.TYPE_CLASS_TEXT
) { _, input ->
LyricUtil.writeLrc(song, input.toString())
}
positiveButton(res = R.string.save) {
(lyricsSectionsAdapter.fragments[0].first as SyncedLyrics).loadLRCLyrics()
}
negativeButton(res = android.R.string.cancel)
}
}
class LyricsSectionsAdapter(fragmentActivity: FragmentActivity) :
FragmentStateAdapter(fragmentActivity) {
val fragments = listOf(
Pair(SyncedLyrics(), R.string.synced_lyrics),
Pair(NormalLyrics(), R.string.normal_lyrics)
)
override fun getItemCount(): Int {
return fragments.size
}
override fun createFragment(position: Int): Fragment {
return fragments[position].first
}
}
class NormalLyrics : AbsMusicServiceFragment(R.layout.fragment_normal_lyrics) {
private var _binding: FragmentNormalLyricsBinding? = null
private val binding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
_binding = FragmentNormalLyricsBinding.bind(view)
loadNormalLyrics()
super.onViewCreated(view, savedInstanceState)
}
fun loadNormalLyrics() {
var lyrics: String? = null
val file = File(MusicPlayerRemote.currentSong.data)
try {
lyrics = AudioFileIO.read(file).tagOrCreateDefault.getFirst(FieldKey.LYRICS)
} catch (e: Exception) {
e.printStackTrace()
}
if (lyrics.isNullOrEmpty()) {
binding.noLyricsFound.visibility = View.VISIBLE
} else {
binding.noLyricsFound.visibility = View.GONE
}
binding.normalLyrics.text = lyrics
}
override fun onPlayingMetaChanged() {
super.onPlayingMetaChanged()
loadNormalLyrics()
}
override fun onServiceConnected() {
super.onServiceConnected()
loadNormalLyrics()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
class SyncedLyrics : AbsMusicServiceFragment(R.layout.fragment_synced_lyrics),
MusicProgressViewUpdateHelper.Callback {
private var _binding: FragmentSyncedLyricsBinding? = null
private val binding get() = _binding!!
private lateinit var updateHelper: MusicProgressViewUpdateHelper
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
updateHelper = MusicProgressViewUpdateHelper(this, 500, 1000)
_binding = FragmentSyncedLyricsBinding.bind(view)
setupLyricsView()
loadLRCLyrics()
super.onViewCreated(view, savedInstanceState)
}
fun loadLRCLyrics() {
binding.lyricsView.setLabel("Empty")
val song = MusicPlayerRemote.currentSong
if (LyricUtil.isLrcOriginalFileExist(song.data)) {
binding.lyricsView.loadLrc(LyricUtil.getLocalLyricOriginalFile(song.data))
} else if (LyricUtil.isLrcFileExist(song.title, song.artistName)) {
binding.lyricsView.loadLrc(LyricUtil.getLocalLyricFile(song.title, song.artistName))
}
}
private fun setupLyricsView() {
binding.lyricsView.apply {
setCurrentColor(ThemeStore.accentColor(context))
setTimeTextColor(ThemeStore.accentColor(context))
setTimelineColor(ThemeStore.accentColor(context))
setTimelineTextColor(ThemeStore.accentColor(context))
setDraggable(true, LrcView.OnPlayClickListener {
MusicPlayerRemote.seekTo(it.toInt())
return@OnPlayClickListener true
})
}
}
override fun onUpdateProgressViews(progress: Int, total: Int) {
binding.lyricsView.updateTime(progress.toLong())
}
override fun onPlayingMetaChanged() {
super.onPlayingMetaChanged()
loadLRCLyrics()
}
override fun onServiceConnected() {
super.onServiceConnected()
loadLRCLyrics()
}
override fun onResume() {
super.onResume()
updateHelper.start()
}
override fun onPause() {
super.onPause()
updateHelper.stop()
}
}
} }

View file

@ -20,38 +20,15 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.provider.MediaStore import android.provider.MediaStore
import android.view.View
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.ui.NavigationUI import androidx.navigation.ui.setupWithNavController
import code.name.monkey.retromusic.ADAPTIVE_COLOR_APP import code.name.monkey.retromusic.*
import code.name.monkey.retromusic.ALBUM_COVER_STYLE import code.name.monkey.retromusic.activities.base.AbsCastActivity
import code.name.monkey.retromusic.ALBUM_COVER_TRANSFORM import code.name.monkey.retromusic.databinding.SlidingMusicPanelLayoutBinding
import code.name.monkey.retromusic.BANNER_IMAGE_PATH
import code.name.monkey.retromusic.BLACK_THEME
import code.name.monkey.retromusic.CAROUSEL_EFFECT
import code.name.monkey.retromusic.CIRCULAR_ALBUM_ART
import code.name.monkey.retromusic.DESATURATED_COLOR
import code.name.monkey.retromusic.EXTRA_SONG_INFO
import code.name.monkey.retromusic.GENERAL_THEME
import code.name.monkey.retromusic.HOME_ARTIST_GRID_STYLE
import code.name.monkey.retromusic.KEEP_SCREEN_ON
import code.name.monkey.retromusic.LANGUAGE_NAME
import code.name.monkey.retromusic.LIBRARY_CATEGORIES
import code.name.monkey.retromusic.NOW_PLAYING_SCREEN_ID
import code.name.monkey.retromusic.PROFILE_IMAGE_PATH
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.ROUND_CORNERS
import code.name.monkey.retromusic.TAB_TEXT_MODE
import code.name.monkey.retromusic.TOGGLE_ADD_CONTROLS
import code.name.monkey.retromusic.TOGGLE_FULL_SCREEN
import code.name.monkey.retromusic.TOGGLE_GENRE
import code.name.monkey.retromusic.TYPE_HOME_BANNER
import code.name.monkey.retromusic.TOGGLE_SEPARATE_LINE
import code.name.monkey.retromusic.TOGGLE_VOLUME
import code.name.monkey.retromusic.USER_NAME
import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity
import code.name.monkey.retromusic.extensions.extra import code.name.monkey.retromusic.extensions.extra
import code.name.monkey.retromusic.extensions.findNavController import code.name.monkey.retromusic.extensions.findNavController
import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewFragment
import code.name.monkey.retromusic.fragments.home.HomeFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.SearchQueryHelper.getSongs import code.name.monkey.retromusic.helper.SearchQueryHelper.getSongs
import code.name.monkey.retromusic.model.CategoryInfo import code.name.monkey.retromusic.model.CategoryInfo
@ -64,13 +41,13 @@ import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.android.ext.android.get import org.koin.android.ext.android.get
class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeListener { class MainActivity : AbsCastActivity(), OnSharedPreferenceChangeListener {
companion object { companion object {
const val TAG = "MainActivity" const val TAG = "MainActivity"
const val EXPAND_PANEL = "expand_panel" const val EXPAND_PANEL = "expand_panel"
} }
override fun createContentView(): View { override fun createContentView(): SlidingMusicPanelLayoutBinding {
return wrapSlidingMusicPanel() return wrapSlidingMusicPanel()
} }
@ -98,10 +75,50 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis
val categoryInfo: CategoryInfo = PreferenceUtil.libraryCategory.first { it.visible } val categoryInfo: CategoryInfo = PreferenceUtil.libraryCategory.first { it.visible }
if (categoryInfo.visible) { if (categoryInfo.visible) {
navGraph.startDestination = categoryInfo.category.id navGraph.setStartDestination(
if (PreferenceUtil.rememberLastTab) {
PreferenceUtil.lastTab.let {
if (it == 0) {
categoryInfo.category.id
} else {
it
}
}
} else categoryInfo.category.id
)
} }
navController.graph = navGraph navController.graph = navGraph
NavigationUI.setupWithNavController(getBottomNavigationView(), navController) getBottomNavigationView().setupWithNavController(navController)
// Scroll Fragment to top
getBottomNavigationView().setOnItemReselectedListener {
supportFragmentManager.findFragmentById(R.id.fragment_container)?.childFragmentManager?.fragments?.get(0)
.also {
if (it is AbsRecyclerViewFragment<*, *>) {
it.scrollToTop()
}
if (it is HomeFragment) {
it.scrollToTop()
}
}
}
navController.addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
R.id.action_home, R.id.action_song, R.id.action_album, R.id.action_artist, R.id.action_folder, R.id.action_playlist, R.id.action_genre -> {
// Save the last tab
if (PreferenceUtil.rememberLastTab) {
saveTab(destination.id)
}
// Show Bottom Navigation Bar
setBottomBarVisibility(true)
}
else -> setBottomBarVisibility(false) // Hide Bottom Navigation Bar
}
}
}
private fun saveTab(id: Int) {
PreferenceUtil.lastTab = id
} }
override fun onSupportNavigateUp(): Boolean = override fun onSupportNavigateUp(): Boolean =
@ -112,6 +129,8 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis
PreferenceUtil.registerOnSharedPreferenceChangedListener(this) PreferenceUtil.registerOnSharedPreferenceChangedListener(this)
val expand = extra<Boolean>(EXPAND_PANEL).value ?: false val expand = extra<Boolean>(EXPAND_PANEL).value ?: false
if (expand && PreferenceUtil.isExpandPanel) { if (expand && PreferenceUtil.isExpandPanel) {
setBottomBarVisibility(false)
fromNotification = true
expandPanel() expandPanel()
intent.removeExtra(EXPAND_PANEL) intent.removeExtra(EXPAND_PANEL)
} }
@ -123,7 +142,7 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis
} }
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (key == GENERAL_THEME || key == BLACK_THEME || key == ADAPTIVE_COLOR_APP || key == USER_NAME || key == TOGGLE_FULL_SCREEN || key == TOGGLE_VOLUME || key == ROUND_CORNERS || key == CAROUSEL_EFFECT || key == NOW_PLAYING_SCREEN_ID || key == TOGGLE_GENRE || key == BANNER_IMAGE_PATH || key == PROFILE_IMAGE_PATH || key == CIRCULAR_ALBUM_ART || key == KEEP_SCREEN_ON || key == TOGGLE_SEPARATE_LINE || key == TYPE_HOME_BANNER || key == TOGGLE_ADD_CONTROLS || key == ALBUM_COVER_STYLE || key == HOME_ARTIST_GRID_STYLE || key == ALBUM_COVER_TRANSFORM || key == DESATURATED_COLOR || key == EXTRA_SONG_INFO || key == TAB_TEXT_MODE || key == LANGUAGE_NAME || key == LIBRARY_CATEGORIES) { if (key == GENERAL_THEME || key == BLACK_THEME || key == ADAPTIVE_COLOR_APP || key == USER_NAME || key == TOGGLE_FULL_SCREEN || key == TOGGLE_VOLUME || key == ROUND_CORNERS || key == CAROUSEL_EFFECT || key == NOW_PLAYING_SCREEN_ID || key == TOGGLE_GENRE || key == BANNER_IMAGE_PATH || key == PROFILE_IMAGE_PATH || key == CIRCULAR_ALBUM_ART || key == KEEP_SCREEN_ON || key == TOGGLE_SEPARATE_LINE || key == TOGGLE_HOME_BANNER || key == TOGGLE_ADD_CONTROLS || key == ALBUM_COVER_STYLE || key == HOME_ARTIST_GRID_STYLE || key == ALBUM_COVER_TRANSFORM || key == DESATURATED_COLOR || key == EXTRA_SONG_INFO || key == TAB_TEXT_MODE || key == LANGUAGE_NAME || key == LIBRARY_CATEGORIES) {
postRecreate() postRecreate()
} }
} }

View file

@ -14,44 +14,51 @@
*/ */
package code.name.monkey.retromusic.activities package code.name.monkey.retromusic.activities
import android.Manifest
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.ColorStateList
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.Settings import android.provider.Settings
import android.view.View
import androidx.annotation.RequiresApi
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.VersionUtils 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.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.databinding.ActivityPermissionBinding
import code.name.monkey.retromusic.extensions.accentBackgroundColor import code.name.monkey.retromusic.extensions.accentBackgroundColor
import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.util.RingtoneManager import code.name.monkey.retromusic.util.RingtoneManager
import kotlinx.android.synthetic.main.activity_permission.*
import kotlinx.android.synthetic.main.fragment_library.appNameText
class PermissionActivity : AbsMusicServiceActivity() { class PermissionActivity : AbsMusicServiceActivity() {
private lateinit var binding: ActivityPermissionBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView((R.layout.activity_permission)) binding = ActivityPermissionBinding.inflate(layoutInflater)
setContentView(binding.root)
setStatusbarColorAuto() setStatusbarColorAuto()
setNavigationbarColorAuto() setNavigationbarColorAuto()
setLightNavigationBar(true) setLightNavigationBar(true)
setTaskDescriptionColorAuto() setTaskDescriptionColorAuto()
setupTitle() setupTitle()
storagePermission.setButtonClick { binding.storagePermission.setButtonClick {
requestPermissions() requestPermissions()
} }
if (VersionUtils.hasMarshmallow()) audioPermission.show() if (VersionUtils.hasMarshmallow()) binding.audioPermission.show()
audioPermission.setButtonClick { binding.audioPermission.setButtonClick {
if (RingtoneManager.requiresDialog(this@PermissionActivity)) { if (RingtoneManager.requiresDialog(this@PermissionActivity)) {
val intent = Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS) val intent = Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS)
intent.data = Uri.parse("package:" + applicationContext.packageName) intent.data = Uri.parse("package:" + applicationContext.packageName)
startActivity(intent) startActivity(intent)
} }
} }
finish.accentBackgroundColor() binding.finish.accentBackgroundColor()
finish.setOnClickListener { binding.finish.setOnClickListener {
if (hasPermissions()) { if (hasPermissions()) {
startActivity( startActivity(
Intent(this, MainActivity::class.java).addFlags( Intent(this, MainActivity::class.java).addFlags(
@ -71,6 +78,32 @@ class PermissionActivity : AbsMusicServiceActivity() {
"Hello there! <br>Welcome to <b>Retro <span style='color:$hexColor';>Music</span></b>", "Hello there! <br>Welcome to <b>Retro <span style='color:$hexColor';>Music</span></b>",
HtmlCompat.FROM_HTML_MODE_COMPACT HtmlCompat.FROM_HTML_MODE_COMPACT
) )
appNameText.text = appName binding.appNameText.text = appName
}
@RequiresApi(Build.VERSION_CODES.M)
override fun onResume() {
if (hasStoragePermission()) {
binding.storagePermission.checkImage.visibility = View.VISIBLE
binding.storagePermission.checkImage.imageTintList =
ColorStateList.valueOf(ThemeStore.accentColor(this))
}
if (hasAudioPermission()) {
binding.audioPermission.checkImage.visibility = View.VISIBLE
binding.audioPermission.checkImage.imageTintList =
ColorStateList.valueOf(ThemeStore.accentColor(this))
}
super.onResume()
}
@RequiresApi(Build.VERSION_CODES.M)
private fun hasStoragePermission(): Boolean {
return checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
}
@RequiresApi(Build.VERSION_CODES.M)
private fun hasAudioPermission(): Boolean {
return Settings.System.canWrite(this)
} }
} }

View file

@ -24,6 +24,7 @@ import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.adapter.song.PlayingQueueAdapter import code.name.monkey.retromusic.adapter.song.PlayingQueueAdapter
import code.name.monkey.retromusic.databinding.ActivityPlayingQueueBinding
import code.name.monkey.retromusic.extensions.accentColor import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.surfaceColor import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
@ -34,10 +35,10 @@ import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropM
import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager
import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils
import kotlinx.android.synthetic.main.activity_playing_queue.*
open class PlayingQueueActivity : AbsMusicServiceActivity() { open class PlayingQueueActivity : AbsMusicServiceActivity() {
private lateinit var binding: ActivityPlayingQueueBinding
private var wrappedAdapter: RecyclerView.Adapter<*>? = null private var wrappedAdapter: RecyclerView.Adapter<*>? = null
private var recyclerViewDragDropManager: RecyclerViewDragDropManager? = null private var recyclerViewDragDropManager: RecyclerViewDragDropManager? = null
private var recyclerViewSwipeManager: RecyclerViewSwipeManager? = null private var recyclerViewSwipeManager: RecyclerViewSwipeManager? = null
@ -56,7 +57,8 @@ open class PlayingQueueActivity : AbsMusicServiceActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar() setDrawUnderStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_playing_queue) binding = ActivityPlayingQueueBinding.inflate(layoutInflater)
setContentView(binding.root)
setStatusbarColorAuto() setStatusbarColorAuto()
setNavigationbarColorAuto() setNavigationbarColorAuto()
setTaskDescriptionColorAuto() setTaskDescriptionColorAuto()
@ -65,7 +67,7 @@ open class PlayingQueueActivity : AbsMusicServiceActivity() {
setupToolbar() setupToolbar()
setUpRecyclerView() setUpRecyclerView()
clearQueue.setOnClickListener { binding.clearQueue.setOnClickListener {
MusicPlayerRemote.clearQueue() MusicPlayerRemote.clearQueue()
} }
checkForPadding() checkForPadding()
@ -100,25 +102,25 @@ open class PlayingQueueActivity : AbsMusicServiceActivity() {
linearLayoutManager = LinearLayoutManager(this) linearLayoutManager = LinearLayoutManager(this)
recyclerView.layoutManager = linearLayoutManager binding.recyclerView.layoutManager = linearLayoutManager
recyclerView.adapter = wrappedAdapter binding.recyclerView.adapter = wrappedAdapter
recyclerView.itemAnimator = animator binding.recyclerView.itemAnimator = animator
recyclerViewTouchActionGuardManager?.attachRecyclerView(recyclerView) recyclerViewTouchActionGuardManager?.attachRecyclerView(binding.recyclerView)
recyclerViewDragDropManager?.attachRecyclerView(recyclerView) recyclerViewDragDropManager?.attachRecyclerView(binding.recyclerView)
recyclerViewSwipeManager?.attachRecyclerView(recyclerView) recyclerViewSwipeManager?.attachRecyclerView(binding.recyclerView)
linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0) linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0)
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy) super.onScrolled(recyclerView, dx, dy)
if (dy > 0) { if (dy > 0) {
clearQueue.shrink() binding.clearQueue.shrink()
} else if (dy < 0) { } else if (dy < 0) {
clearQueue.extend() binding.clearQueue.extend()
} }
} }
}) })
ThemedFastScroller.create(recyclerView) ThemedFastScroller.create(binding.recyclerView)
} }
private fun checkForPadding() { private fun checkForPadding() {
@ -140,7 +142,7 @@ open class PlayingQueueActivity : AbsMusicServiceActivity() {
} }
private fun updateCurrentSong() { private fun updateCurrentSong() {
toolbar.subtitle = getUpNextAndQueueTime() binding.toolbar.subtitle = getUpNextAndQueueTime()
} }
override fun onPlayingMetaChanged() { override fun onPlayingMetaChanged() {
@ -150,7 +152,7 @@ open class PlayingQueueActivity : AbsMusicServiceActivity() {
private fun updateQueuePosition() { private fun updateQueuePosition() {
playingQueueAdapter?.setCurrent(MusicPlayerRemote.position) playingQueueAdapter?.setCurrent(MusicPlayerRemote.position)
resetToCurrentPosition() resetToCurrentPosition()
toolbar.subtitle = getUpNextAndQueueTime() binding.toolbar.subtitle = getUpNextAndQueueTime()
} }
private fun updateQueue() { private fun updateQueue() {
@ -159,7 +161,7 @@ open class PlayingQueueActivity : AbsMusicServiceActivity() {
} }
private fun resetToCurrentPosition() { private fun resetToCurrentPosition() {
recyclerView.stopScroll() binding.recyclerView.stopScroll()
linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0) linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0)
} }
@ -188,15 +190,15 @@ open class PlayingQueueActivity : AbsMusicServiceActivity() {
} }
private fun setupToolbar() { private fun setupToolbar() {
toolbar.subtitle = getUpNextAndQueueTime() binding.toolbar.subtitle = getUpNextAndQueueTime()
toolbar.setBackgroundColor(surfaceColor()) binding.toolbar.setBackgroundColor(surfaceColor())
setSupportActionBar(toolbar) setSupportActionBar(binding.toolbar)
clearQueue.backgroundTintList = ColorStateList.valueOf(accentColor()) binding.clearQueue.backgroundTintList = ColorStateList.valueOf(accentColor())
ColorStateList.valueOf( ColorStateList.valueOf(
MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(accentColor())) MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(accentColor()))
).apply { ).apply {
clearQueue.setTextColor(this) binding.clearQueue.setTextColor(this)
clearQueue.iconTint = this binding.clearQueue.iconTint = this
} }
} }
} }

View file

@ -29,43 +29,45 @@ import code.name.monkey.retromusic.BuildConfig
import code.name.monkey.retromusic.Constants.PRO_VERSION_PRODUCT_ID import code.name.monkey.retromusic.Constants.PRO_VERSION_PRODUCT_ID
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsBaseActivity import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.databinding.ActivityProVersionBinding
import com.anjlab.android.iab.v3.BillingProcessor import com.anjlab.android.iab.v3.BillingProcessor
import com.anjlab.android.iab.v3.TransactionDetails import com.anjlab.android.iab.v3.TransactionDetails
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import kotlinx.android.synthetic.main.activity_pro_version.*
class PurchaseActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler { class PurchaseActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler {
private lateinit var binding: ActivityProVersionBinding
private lateinit var billingProcessor: BillingProcessor private lateinit var billingProcessor: BillingProcessor
private var restorePurchaseAsyncTask: AsyncTask<*, *, *>? = null private var restorePurchaseAsyncTask: AsyncTask<*, *, *>? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar() setDrawUnderStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pro_version) binding = ActivityProVersionBinding.inflate(layoutInflater)
setContentView(binding.root)
setStatusbarColor(Color.TRANSPARENT) setStatusbarColor(Color.TRANSPARENT)
setLightStatusbar(false) setLightStatusbar(false)
setNavigationbarColor(Color.BLACK) setNavigationbarColor(Color.BLACK)
setLightNavigationBar(false) setLightNavigationBar(false)
toolbar.navigationIcon?.setTint(Color.WHITE) binding.toolbar.navigationIcon?.setTint(Color.WHITE)
toolbar.setNavigationOnClickListener { onBackPressed() } binding.toolbar.setNavigationOnClickListener { onBackPressed() }
restoreButton.isEnabled = false binding.restoreButton.isEnabled = false
purchaseButton.isEnabled = false binding.purchaseButton.isEnabled = false
billingProcessor = BillingProcessor(this, BuildConfig.GOOGLE_PLAY_LICENSING_KEY, this) billingProcessor = BillingProcessor(this, BuildConfig.GOOGLE_PLAY_LICENSING_KEY, this)
MaterialUtil.setTint(purchaseButton, true) MaterialUtil.setTint(binding.purchaseButton, true)
restoreButton.setOnClickListener { binding.restoreButton.setOnClickListener {
if (restorePurchaseAsyncTask == null || restorePurchaseAsyncTask!!.status != AsyncTask.Status.RUNNING) { if (restorePurchaseAsyncTask == null || restorePurchaseAsyncTask!!.status != AsyncTask.Status.RUNNING) {
restorePurchase() restorePurchase()
} }
} }
purchaseButton.setOnClickListener { binding.purchaseButton.setOnClickListener {
billingProcessor.purchase(this@PurchaseActivity, PRO_VERSION_PRODUCT_ID) billingProcessor.purchase(this@PurchaseActivity, PRO_VERSION_PRODUCT_ID)
} }
bannerContainer.backgroundTintList = binding.bannerContainer.backgroundTintList =
ColorStateList.valueOf(ThemeStore.accentColor(this)) ColorStateList.valueOf(ThemeStore.accentColor(this))
} }
@ -99,8 +101,8 @@ class PurchaseActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler {
} }
override fun onBillingInitialized() { override fun onBillingInitialized() {
restoreButton.isEnabled = true binding.restoreButton.isEnabled = true
purchaseButton.isEnabled = true binding.purchaseButton.isEnabled = true
} }
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {

View file

@ -23,16 +23,19 @@ import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsBaseActivity import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.appshortcuts.DynamicShortcutManager import code.name.monkey.retromusic.appshortcuts.DynamicShortcutManager
import code.name.monkey.retromusic.databinding.ActivitySettingsBinding
import code.name.monkey.retromusic.extensions.applyToolbar import code.name.monkey.retromusic.extensions.applyToolbar
import code.name.monkey.retromusic.extensions.findNavController import code.name.monkey.retromusic.extensions.findNavController
import com.afollestad.materialdialogs.color.ColorChooserDialog import com.afollestad.materialdialogs.MaterialDialog
import kotlinx.android.synthetic.main.activity_settings.* import com.afollestad.materialdialogs.color.ColorCallback
class SettingsActivity : AbsBaseActivity(), ColorChooserDialog.ColorCallback { class SettingsActivity : AbsBaseActivity(), ColorCallback {
private lateinit var binding: ActivitySettingsBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar() setDrawUnderStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings) binding = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(binding.root)
setStatusbarColorAuto() setStatusbarColorAuto()
setNavigationbarColorAuto() setNavigationbarColorAuto()
setLightNavigationBar(true) setLightNavigationBar(true)
@ -41,10 +44,11 @@ class SettingsActivity : AbsBaseActivity(), ColorChooserDialog.ColorCallback {
private fun setupToolbar() { private fun setupToolbar() {
setTitle(R.string.action_settings) setTitle(R.string.action_settings)
applyToolbar(toolbar) applyToolbar(binding.toolbar)
val navController: NavController = findNavController(R.id.contentFrame) val navController: NavController = findNavController(R.id.contentFrame)
navController.addOnDestinationChangedListener { _, _, _ -> navController.addOnDestinationChangedListener { _, _, _ ->
toolbar.title = navController.currentDestination?.let { getStringFromDestination(it) } binding.toolbar.title =
navController.currentDestination?.let { getStringFromDestination(it) }
} }
} }
@ -68,24 +72,18 @@ class SettingsActivity : AbsBaseActivity(), ColorChooserDialog.ColorCallback {
return findNavController(R.id.contentFrame).navigateUp() || super.onSupportNavigateUp() return findNavController(R.id.contentFrame).navigateUp() || super.onSupportNavigateUp()
} }
override fun onColorSelection(dialog: ColorChooserDialog, selectedColor: Int) {
when (dialog.title) {
R.string.accent_color -> {
ThemeStore.editTheme(this).accentColor(selectedColor).commit()
if (VersionUtils.hasNougatMR())
DynamicShortcutManager(this).updateDynamicShortcuts()
}
}
recreate()
}
override fun onColorChooserDismissed(dialog: ColorChooserDialog) {
}
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) { if (item.itemId == android.R.id.home) {
onBackPressed() onBackPressed()
} }
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
override fun invoke(dialog: MaterialDialog, color: Int) {
ThemeStore.editTheme(this).accentColor(color).commit()
if (VersionUtils.hasNougatMR())
DynamicShortcutManager(this).updateDynamicShortcuts()
recreate()
}
} }

View file

@ -26,15 +26,14 @@ import androidx.core.view.drawToBitmap
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsBaseActivity import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.databinding.ActivityShareInstagramBinding
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.Share import code.name.monkey.retromusic.util.Share
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.activity_share_instagram.*
/** /**
* Created by hemanths on 2020-02-02. * Created by hemanths on 2020-02-02.
@ -42,6 +41,8 @@ import kotlinx.android.synthetic.main.activity_share_instagram.*
class ShareInstagramStory : AbsBaseActivity() { class ShareInstagramStory : AbsBaseActivity() {
private lateinit var binding: ActivityShareInstagramBinding
companion object { companion object {
const val EXTRA_SONG = "extra_song" const val EXTRA_SONG = "extra_song"
} }
@ -57,32 +58,33 @@ class ShareInstagramStory : AbsBaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar() setDrawUnderStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_share_instagram) binding = ActivityShareInstagramBinding.inflate(layoutInflater)
setContentView(binding.root)
setStatusbarColor(Color.TRANSPARENT) setStatusbarColor(Color.TRANSPARENT)
setNavigationbarColor(Color.BLACK) setNavigationbarColor(Color.BLACK)
toolbar.setBackgroundColor(Color.TRANSPARENT) binding.toolbar.setBackgroundColor(Color.TRANSPARENT)
setSupportActionBar(toolbar) setSupportActionBar(binding.toolbar)
val song = intent.extras?.getParcelable<Song>(EXTRA_SONG) val song = intent.extras?.getParcelable<Song>(EXTRA_SONG)
song?.let { songFinal -> song?.let { songFinal ->
SongGlideRequest.Builder.from(Glide.with(this), songFinal) GlideApp.with(this)
.checkIgnoreMediaStore(this@ShareInstagramStory) .asBitmapPalette()
.generatePalette(this@ShareInstagramStory) .songCoverOptions(songFinal)
.build() .load(RetroGlideExtension.getSongModel(songFinal))
.into(object : RetroMusicColoredTarget(image) { .into(object : RetroMusicColoredTarget(binding.image) {
override fun onColorReady(colors: MediaNotificationProcessor) { override fun onColorReady(colors: MediaNotificationProcessor) {
val isColorLight = ColorUtil.isColorLight(colors.backgroundColor) val isColorLight = ColorUtil.isColorLight(colors.backgroundColor)
setColors(isColorLight, colors.backgroundColor) setColors(isColorLight, colors.backgroundColor)
} }
}) })
shareTitle.text = songFinal.title binding.shareTitle.text = songFinal.title
shareText.text = songFinal.artistName binding.shareText.text = songFinal.artistName
shareButton.setOnClickListener { binding.shareButton.setOnClickListener {
val path: String = Media.insertImage( val path: String = Media.insertImage(
contentResolver, contentResolver,
mainContent.drawToBitmap(Bitmap.Config.ARGB_8888), binding.mainContent.drawToBitmap(Bitmap.Config.ARGB_8888),
"Design", null "Design", null
) )
val uri = Uri.parse(path) val uri = Uri.parse(path)
@ -92,24 +94,25 @@ class ShareInstagramStory : AbsBaseActivity() {
) )
} }
} }
shareButton.setTextColor( binding.shareButton.setTextColor(
MaterialValueHelper.getPrimaryTextColor( MaterialValueHelper.getPrimaryTextColor(
this, this,
ColorUtil.isColorLight(ThemeStore.accentColor(this)) ColorUtil.isColorLight(ThemeStore.accentColor(this))
) )
) )
shareButton.backgroundTintList = ColorStateList.valueOf(ThemeStore.accentColor(this)) binding.shareButton.backgroundTintList =
ColorStateList.valueOf(ThemeStore.accentColor(this))
} }
private fun setColors(colorLight: Boolean, color: Int) { private fun setColors(colorLight: Boolean, color: Int) {
setLightStatusbar(colorLight) setLightStatusbar(colorLight)
toolbar.setTitleTextColor( binding.toolbar.setTitleTextColor(
MaterialValueHelper.getPrimaryTextColor( MaterialValueHelper.getPrimaryTextColor(
this@ShareInstagramStory, this@ShareInstagramStory,
colorLight colorLight
) )
) )
toolbar.navigationIcon?.setTintList( binding.toolbar.navigationIcon?.setTintList(
ColorStateList.valueOf( ColorStateList.valueOf(
MaterialValueHelper.getPrimaryTextColor( MaterialValueHelper.getPrimaryTextColor(
this@ShareInstagramStory, this@ShareInstagramStory,
@ -117,7 +120,7 @@ class ShareInstagramStory : AbsBaseActivity() {
) )
) )
) )
mainContent.background = binding.mainContent.background =
GradientDrawable( GradientDrawable(
GradientDrawable.Orientation.TOP_BOTTOM, GradientDrawable.Orientation.TOP_BOTTOM,
intArrayOf(color, Color.BLACK) intArrayOf(color, Color.BLACK)

View file

@ -37,6 +37,7 @@ import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.BuildConfig import code.name.monkey.retromusic.BuildConfig
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsBaseActivity import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.databinding.ActivityDonationBinding
import code.name.monkey.retromusic.extensions.textColorPrimary import code.name.monkey.retromusic.extensions.textColorPrimary
import code.name.monkey.retromusic.extensions.textColorSecondary import code.name.monkey.retromusic.extensions.textColorSecondary
import com.anjlab.android.iab.v3.BillingProcessor import com.anjlab.android.iab.v3.BillingProcessor
@ -44,10 +45,11 @@ import com.anjlab.android.iab.v3.SkuDetails
import com.anjlab.android.iab.v3.TransactionDetails import com.anjlab.android.iab.v3.TransactionDetails
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.util.* import java.util.*
import kotlinx.android.synthetic.main.activity_donation.*
class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler { class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler {
lateinit var binding: ActivityDonationBinding
companion object { companion object {
val TAG: String = SupportDevelopmentActivity::class.java.simpleName val TAG: String = SupportDevelopmentActivity::class.java.simpleName
const val DONATION_PRODUCT_IDS = R.array.donation_ids const val DONATION_PRODUCT_IDS = R.array.donation_ids
@ -72,6 +74,7 @@ class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingH
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityDonationBinding.inflate(layoutInflater)
setContentView(R.layout.activity_donation) setContentView(R.layout.activity_donation)
setStatusbarColorAuto() setStatusbarColorAuto()
@ -82,15 +85,15 @@ class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingH
setupToolbar() setupToolbar()
billingProcessor = BillingProcessor(this, BuildConfig.GOOGLE_PLAY_LICENSING_KEY, this) billingProcessor = BillingProcessor(this, BuildConfig.GOOGLE_PLAY_LICENSING_KEY, this)
TintHelper.setTint(progress, ThemeStore.accentColor(this)) TintHelper.setTint(binding.progress, ThemeStore.accentColor(this))
donation.setTextColor(ThemeStore.accentColor(this)) binding.donation.setTextColor(ThemeStore.accentColor(this))
} }
private fun setupToolbar() { private fun setupToolbar() {
val toolbarColor = ATHUtil.resolveColor(this, R.attr.colorSurface) val toolbarColor = ATHUtil.resolveColor(this, R.attr.colorSurface)
toolbar.setBackgroundColor(toolbarColor) binding.toolbar.setBackgroundColor(toolbarColor)
ToolbarContentTintHelper.colorBackButton(toolbar) ToolbarContentTintHelper.colorBackButton(binding.toolbar)
setSupportActionBar(toolbar) setSupportActionBar(binding.toolbar)
} }
override fun onBillingInitialized() { override fun onBillingInitialized() {
@ -146,8 +149,8 @@ private class SkuDetailsLoadAsyncTask(supportDevelopmentActivity: SupportDevelop
super.onPreExecute() super.onPreExecute()
val supportDevelopmentActivity = weakReference.get() ?: return val supportDevelopmentActivity = weakReference.get() ?: return
supportDevelopmentActivity.progressContainer.visibility = View.VISIBLE supportDevelopmentActivity.binding.progressContainer.visibility = View.VISIBLE
supportDevelopmentActivity.recyclerView.visibility = View.GONE supportDevelopmentActivity.binding.recyclerView.visibility = View.GONE
} }
override fun doInBackground(vararg params: Void): List<SkuDetails>? { override fun doInBackground(vararg params: Void): List<SkuDetails>? {
@ -166,15 +169,17 @@ private class SkuDetailsLoadAsyncTask(supportDevelopmentActivity: SupportDevelop
val dialog = weakReference.get() ?: return val dialog = weakReference.get() ?: return
if (skuDetails == null || skuDetails.isEmpty()) { if (skuDetails == null || skuDetails.isEmpty()) {
dialog.progressContainer.visibility = View.GONE dialog.binding.progressContainer.visibility = View.GONE
return return
} }
dialog.progressContainer.visibility = View.GONE dialog.binding.progressContainer.visibility = View.GONE
dialog.recyclerView.itemAnimator = DefaultItemAnimator() dialog.binding.recyclerView.apply {
dialog.recyclerView.layoutManager = GridLayoutManager(dialog, 2) itemAnimator = DefaultItemAnimator()
dialog.recyclerView.adapter = SkuDetailsAdapter(dialog, skuDetails) layoutManager = GridLayoutManager(dialog, 2)
dialog.recyclerView.visibility = View.VISIBLE adapter = SkuDetailsAdapter(dialog, skuDetails)
visibility = View.VISIBLE
}
} }
} }

View file

@ -27,54 +27,58 @@ import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.Constants.USER_BANNER import code.name.monkey.retromusic.Constants.USER_BANNER
import code.name.monkey.retromusic.Constants.USER_PROFILE import code.name.monkey.retromusic.Constants.USER_PROFILE
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsBaseActivity import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.databinding.ActivityUserInfoBinding
import code.name.monkey.retromusic.extensions.accentColor import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.applyToolbar import code.name.monkey.retromusic.extensions.applyToolbar
import code.name.monkey.retromusic.glide.ProfileBannerGlideRequest import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.UserProfileGlideRequest import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.util.ImageUtil import code.name.monkey.retromusic.util.ImageUtil
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.target.Target
import com.github.dhaval2404.imagepicker.ImagePicker import com.github.dhaval2404.imagepicker.ImagePicker
import com.github.dhaval2404.imagepicker.constant.ImageProvider import com.github.dhaval2404.imagepicker.constant.ImageProvider
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import kotlinx.android.synthetic.main.activity_user_info.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
class UserInfoActivity : AbsBaseActivity() { class UserInfoActivity : AbsBaseActivity() {
private lateinit var binding: ActivityUserInfoBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user_info) binding = ActivityUserInfoBinding.inflate(layoutInflater)
setContentView(binding.root)
setStatusbarColorAuto() setStatusbarColorAuto()
setNavigationbarColorAuto() setNavigationbarColorAuto()
setTaskDescriptionColorAuto() setTaskDescriptionColorAuto()
setLightNavigationBar(true) setLightNavigationBar(true)
applyToolbar(toolbar) applyToolbar(binding.toolbar)
nameContainer.accentColor() binding.nameContainer.accentColor()
name.setText(PreferenceUtil.userName) binding.name.setText(PreferenceUtil.userName)
userImage.setOnClickListener { binding.userImage.setOnClickListener {
pickNewPhoto() pickNewPhoto()
} }
bannerImage.setOnClickListener { binding.bannerImage.setOnClickListener {
selectBannerImage() selectBannerImage()
} }
next.setOnClickListener { binding.next.setOnClickListener {
val nameString = name.text.toString().trim { it <= ' ' } val nameString = binding.name.text.toString().trim { it <= ' ' }
if (TextUtils.isEmpty(nameString)) { if (TextUtils.isEmpty(nameString)) {
Toast.makeText(this, "Umm you're name can't be empty!", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Umm you're name can't be empty!", Toast.LENGTH_SHORT).show()
return@setOnClickListener return@setOnClickListener
@ -86,23 +90,24 @@ class UserInfoActivity : AbsBaseActivity() {
val textColor = val textColor =
MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(accentColor())) MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(accentColor()))
next.backgroundTintList = ColorStateList.valueOf(accentColor()) binding.next.backgroundTintList = ColorStateList.valueOf(accentColor())
next.iconTint = ColorStateList.valueOf(textColor) binding.next.iconTint = ColorStateList.valueOf(textColor)
next.setTextColor(textColor) binding.next.setTextColor(textColor)
loadProfile() loadProfile()
} }
private fun loadProfile() { private fun loadProfile() {
bannerImage?.let { binding.bannerImage.let {
ProfileBannerGlideRequest.Builder.from( GlideApp.with(this)
Glide.with(this), .asBitmap()
ProfileBannerGlideRequest.getBannerModel() .load(RetroGlideExtension.getBannerModel())
).build().into(it) .profileBannerOptions(RetroGlideExtension.getBannerModel())
.into(it)
} }
UserProfileGlideRequest.Builder.from( GlideApp.with(this).asBitmap()
Glide.with(this), .load(RetroGlideExtension.getUserModel())
UserProfileGlideRequest.getUserModel() .userProfileOptions(RetroGlideExtension.getUserModel())
).build().into(userImage) .into(binding.userImage)
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
@ -145,31 +150,31 @@ class UserInfoActivity : AbsBaseActivity() {
private fun setAndSaveBannerImage(fileUri: Uri) { private fun setAndSaveBannerImage(fileUri: Uri) {
Glide.with(this) Glide.with(this)
.load(fileUri)
.asBitmap() .asBitmap()
.load(fileUri)
.diskCacheStrategy(DiskCacheStrategy.NONE) .diskCacheStrategy(DiskCacheStrategy.NONE)
.listener(object : RequestListener<Any, Bitmap> { .listener(object : RequestListener<Bitmap> {
override fun onException(
e: java.lang.Exception?,
model: Any?,
target: Target<Bitmap>?,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady( override fun onResourceReady(
resource: Bitmap?, resource: Bitmap?,
model: Any?, model: Any?,
target: Target<Bitmap>?, target: Target<Bitmap>?,
isFromMemoryCache: Boolean, dataSource: DataSource?,
isFirstResource: Boolean isFirstResource: Boolean
): Boolean { ): Boolean {
resource?.let { saveImage(it, USER_BANNER) } resource?.let { saveImage(it, USER_BANNER) }
return false return false
} }
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Bitmap>?,
isFirstResource: Boolean
): Boolean {
return false
}
}) })
.into(bannerImage) .into(binding.bannerImage)
} }
private fun saveImage(bitmap: Bitmap, fileName: String) { private fun saveImage(bitmap: Bitmap, fileName: String) {
@ -195,31 +200,31 @@ class UserInfoActivity : AbsBaseActivity() {
private fun setAndSaveUserImage(fileUri: Uri) { private fun setAndSaveUserImage(fileUri: Uri) {
Glide.with(this) Glide.with(this)
.load(fileUri)
.asBitmap() .asBitmap()
.load(fileUri)
.diskCacheStrategy(DiskCacheStrategy.NONE) .diskCacheStrategy(DiskCacheStrategy.NONE)
.listener(object : RequestListener<Any, Bitmap> { .listener(object : RequestListener<Bitmap> {
override fun onException(
e: java.lang.Exception?,
model: Any?,
target: Target<Bitmap>?,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady( override fun onResourceReady(
resource: Bitmap?, resource: Bitmap?,
model: Any?, model: Any?,
target: Target<Bitmap>?, target: Target<Bitmap>?,
isFromMemoryCache: Boolean, dataSource: DataSource?,
isFirstResource: Boolean isFirstResource: Boolean
): Boolean { ): Boolean {
resource?.let { saveImage(it, USER_PROFILE) } resource?.let { saveImage(it, USER_PROFILE) }
return false return false
} }
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Bitmap>?,
isFirstResource: Boolean
): Boolean {
return false
}
}) })
.into(userImage) .into(binding.userImage)
} }
companion object { companion object {

View file

@ -5,24 +5,30 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.Color; import android.graphics.Color;
import android.os.Bundle; import android.os.Bundle;
import android.webkit.WebView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar; import androidx.core.widget.NestedScrollView;
import code.name.monkey.appthemehelper.ThemeStore;
import code.name.monkey.appthemehelper.util.ATHUtil;
import code.name.monkey.appthemehelper.util.ColorUtil;
import code.name.monkey.appthemehelper.util.MaterialValueHelper;
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.activities.base.AbsBaseActivity;
import code.name.monkey.retromusic.util.PreferenceUtil;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Locale; import java.util.Locale;
import code.name.monkey.appthemehelper.ThemeStore;
import code.name.monkey.appthemehelper.util.ATHUtil;
import code.name.monkey.appthemehelper.util.ColorUtil;
import code.name.monkey.appthemehelper.util.MaterialValueHelper;
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper;
import code.name.monkey.retromusic.Constants;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.activities.base.AbsBaseActivity;
import code.name.monkey.retromusic.databinding.ActivityWhatsNewBinding;
import code.name.monkey.retromusic.extensions.ColorExtKt;
import code.name.monkey.retromusic.util.PreferenceUtil;
import code.name.monkey.retromusic.util.RetroUtil;
public class WhatsNewActivity extends AbsBaseActivity { public class WhatsNewActivity extends AbsBaseActivity {
private static String colorToCSS(int color) { private static String colorToCSS(int color) {
@ -49,16 +55,15 @@ public class WhatsNewActivity extends AbsBaseActivity {
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
setDrawUnderStatusBar(); setDrawUnderStatusBar();
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_whats_new); ActivityWhatsNewBinding binding = ActivityWhatsNewBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setStatusbarColorAuto(); setStatusbarColorAuto();
setNavigationbarColorAuto(); setNavigationbarColorAuto();
setTaskDescriptionColorAuto(); setTaskDescriptionColorAuto();
WebView webView = findViewById(R.id.webView); binding.toolbar.setBackgroundColor(ATHUtil.INSTANCE.resolveColor(this, R.attr.colorSurface));
Toolbar toolbar = findViewById(R.id.toolbar); binding.toolbar.setNavigationOnClickListener(v -> onBackPressed());
toolbar.setBackgroundColor(ATHUtil.INSTANCE.resolveColor(this, R.attr.colorSurface)); ToolbarContentTintHelper.colorBackButton(binding.toolbar);
toolbar.setNavigationOnClickListener(v -> onBackPressed());
ToolbarContentTintHelper.colorBackButton(toolbar);
try { try {
StringBuilder buf = new StringBuilder(); StringBuilder buf = new StringBuilder();
@ -80,6 +85,7 @@ public class WhatsNewActivity extends AbsBaseActivity {
final String contentColor = colorToCSS(Color.parseColor(isDark ? "#ffffff" : "#000000")); final String contentColor = colorToCSS(Color.parseColor(isDark ? "#ffffff" : "#000000"));
final String textColor = colorToCSS(Color.parseColor(isDark ? "#60FFFFFF" : "#80000000")); final String textColor = colorToCSS(Color.parseColor(isDark ? "#60FFFFFF" : "#80000000"));
final String accentColorString = colorToCSS(ThemeStore.Companion.accentColor(this)); final String accentColorString = colorToCSS(ThemeStore.Companion.accentColor(this));
final String cardBackgroundColor = colorToCSS(Color.parseColor(isDark ? "#353535" : "#ffffff"));
final String accentTextColor = final String accentTextColor =
colorToCSS( colorToCSS(
MaterialValueHelper.getPrimaryTextColor( MaterialValueHelper.getPrimaryTextColor(
@ -89,23 +95,34 @@ public class WhatsNewActivity extends AbsBaseActivity {
.replace( .replace(
"{style-placeholder}", "{style-placeholder}",
String.format( String.format(
"body { background-color: %s; color: %s; } li {color: %s;} .colorHeader {background-color: %s; color: %s;} .tag {color: %s;}", "body { background-color: %s; color: %s; } li {color: %s;} h3 {color: %s;} .tag {color: %s;} div{background-color: %s;}",
backgroundColor, backgroundColor,
contentColor, contentColor,
textColor, textColor,
accentColorString, accentColorString,
accentTextColor, accentColorString,
accentColorString)) cardBackgroundColor))
.replace("{link-color}", colorToCSS(ThemeStore.Companion.accentColor(this))) .replace("{link-color}", colorToCSS(ThemeStore.Companion.accentColor(this)))
.replace( .replace(
"{link-color-active}", "{link-color-active}",
colorToCSS( colorToCSS(
ColorUtil.INSTANCE.lightenColor(ThemeStore.Companion.accentColor(this)))); ColorUtil.INSTANCE.lightenColor(ThemeStore.Companion.accentColor(this))));
webView.loadData(changeLog, "text/html", "UTF-8"); binding.webView.loadData(changeLog, "text/html", "UTF-8");
} catch (Throwable e) { } catch (Throwable e) {
webView.loadData( binding.webView.loadData(
"<h1>Unable to load</h1><p>" + e.getLocalizedMessage() + "</p>", "text/html", "UTF-8"); "<h1>Unable to load</h1><p>" + e.getLocalizedMessage() + "</p>", "text/html", "UTF-8");
} }
setChangelogRead(this); setChangelogRead(this);
binding.tgFab.setOnClickListener(v -> RetroUtil.openUrl(this, Constants.TELEGRAM_CHANGE_LOG));
ColorExtKt.accentColor(binding.tgFab);
binding.tgFab.shrink();
binding.container.setOnScrollChangeListener((NestedScrollView.OnScrollChangeListener) (v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
int dy = scrollY - oldScrollY;
if (dy > 0) {
binding.tgFab.shrink();
} else if (dy < 0) {
binding.tgFab.extend();
}
});
} }
} }

View file

@ -0,0 +1,141 @@
package code.name.monkey.retromusic.activities.base
import android.os.Bundle
import android.view.ViewStub
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.cast.CastHelper
import code.name.monkey.retromusic.cast.RetroSessionManager
import code.name.monkey.retromusic.cast.RetroWebServer
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.CastSession
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
import java.util.*
abstract class AbsCastActivity : AbsSlidingMusicPanelActivity() {
private var mCastSession: CastSession? = null
private lateinit var castContext: CastContext
private var webServer: RetroWebServer? = null
private var playServicesAvailable: Boolean = false
private val sessionManagerListener by lazy {
object : RetroSessionManager {
override fun onSessionStarting(castSession: CastSession) {
invalidateOptionsMenu()
webServer = RetroWebServer.getInstance(this@AbsCastActivity)
webServer?.start()
}
override fun onSessionStarted(castSession: CastSession, p1: String) {
invalidateOptionsMenu()
mCastSession = castSession
loadCastQueue(MusicPlayerRemote.position)
inflateCastController()
MusicPlayerRemote.isCasting = true
setAllowDragging(false)
collapsePanel()
}
override fun onSessionEnding(p0: CastSession) {
invalidateOptionsMenu()
webServer?.stop()
}
override fun onSessionEnded(castSession: CastSession, p1: Int) {
invalidateOptionsMenu()
if (mCastSession == castSession) {
mCastSession = null
}
MusicPlayerRemote.isCasting = false
setAllowDragging(true)
}
override fun onSessionResumed(castSession: CastSession, p1: Boolean) {
invalidateOptionsMenu()
mCastSession = castSession
loadCastQueue(MusicPlayerRemote.position)
inflateCastController()
MusicPlayerRemote.isCasting = true
setAllowDragging(false)
collapsePanel()
}
override fun onSessionSuspended(castSession: CastSession, p1: Int) {
invalidateOptionsMenu()
if (mCastSession == castSession) {
mCastSession = null
}
MusicPlayerRemote.isCasting = false
setAllowDragging(true)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
playServicesAvailable = try {
GoogleApiAvailability
.getInstance().isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS
} catch (e: Exception) {
false
}
if (playServicesAvailable) {
setupCast()
}
}
private fun setupCast() {
castContext = CastContext.getSharedInstance(this)
}
override fun onResume() {
if (playServicesAvailable) {
castContext.sessionManager.addSessionManagerListener(
sessionManagerListener,
CastSession::class.java
)
if (mCastSession == null) {
mCastSession = castContext.sessionManager.currentCastSession
}
}
super.onResume()
}
override fun onStop() {
super.onStop()
mCastSession = null
}
private fun songChanged(position: Int) {
loadCastQueue(position)
}
fun loadCastQueue(position: Int) {
if (!MusicPlayerRemote.playingQueue.isNullOrEmpty()) {
mCastSession?.let {
CastHelper.castQueue(
it,
MusicPlayerRemote.playingQueue,
position,
MusicPlayerRemote.songProgressMillis.toLong()
)
}
} else {
mCastSession?.let { CastHelper.castSong(it, MusicPlayerRemote.currentSong) }
}
}
override fun onPlayingMetaChanged() {
super.onPlayingMetaChanged()
if (playServicesAvailable) {
songChanged(MusicPlayerRemote.position)
}
}
fun inflateCastController() {
findViewById<ViewStub>(R.id.cast_stub)?.inflate()
}
}

View file

@ -15,12 +15,7 @@
package code.name.monkey.retromusic.activities.base package code.name.monkey.retromusic.activities.base
import android.Manifest import android.Manifest
import android.content.BroadcastReceiver import android.content.*
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.ServiceConnection
import android.os.Bundle import android.os.Bundle
import android.os.IBinder import android.os.IBinder
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -30,11 +25,11 @@ import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.IMusicServiceEventListener import code.name.monkey.retromusic.interfaces.IMusicServiceEventListener
import code.name.monkey.retromusic.repository.RealRepository import code.name.monkey.retromusic.repository.RealRepository
import code.name.monkey.retromusic.service.MusicService.* import code.name.monkey.retromusic.service.MusicService.*
import java.lang.ref.WeakReference
import java.util.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import java.lang.ref.WeakReference
import java.util.*
abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventListener { abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventListener {
@ -166,6 +161,12 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventLi
} }
} }
override fun onFavoriteStateChanged() {
for (listener in mMusicServiceEventListeners) {
listener.onFavoriteStateChanged()
}
}
override fun onHasPermissionsChanged(hasPermissions: Boolean) { override fun onHasPermissionsChanged(hasPermissions: Boolean) {
super.onHasPermissionsChanged(hasPermissions) super.onHasPermissionsChanged(hasPermissions)
val intent = Intent(MEDIA_STORE_CHANGED) val intent = Intent(MEDIA_STORE_CHANGED)
@ -194,7 +195,8 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventLi
val activity = reference.get() val activity = reference.get()
if (activity != null && action != null) { if (activity != null && action != null) {
when (action) { when (action) {
FAVORITE_STATE_CHANGED, META_CHANGED -> activity.onPlayingMetaChanged() FAVORITE_STATE_CHANGED -> activity.onFavoriteStateChanged()
META_CHANGED -> activity.onPlayingMetaChanged()
QUEUE_CHANGED -> activity.onQueueChanged() QUEUE_CHANGED -> activity.onQueueChanged()
PLAY_STATE_CHANGED -> activity.onPlayStateChanged() PLAY_STATE_CHANGED -> activity.onPlayStateChanged()
REPEAT_MODE_CHANGED -> activity.onRepeatModeChanged() REPEAT_MODE_CHANGED -> activity.onRepeatModeChanged()

View file

@ -14,7 +14,6 @@
*/ */
package code.name.monkey.retromusic.activities.base package code.name.monkey.retromusic.activities.base
import android.annotation.SuppressLint
import android.graphics.Color import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
@ -29,6 +28,8 @@ import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.RetroBottomSheetBehavior import code.name.monkey.retromusic.RetroBottomSheetBehavior
import code.name.monkey.retromusic.databinding.ActivityMainContentBinding
import code.name.monkey.retromusic.databinding.SlidingMusicPanelLayoutBinding
import code.name.monkey.retromusic.extensions.* import code.name.monkey.retromusic.extensions.*
import code.name.monkey.retromusic.fragments.LibraryViewModel import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.fragments.MiniPlayerFragment import code.name.monkey.retromusic.fragments.MiniPlayerFragment
@ -57,12 +58,12 @@ import code.name.monkey.retromusic.model.CategoryInfo
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.views.BottomNavigationBarTinted import code.name.monkey.retromusic.views.BottomNavigationBarTinted
import com.google.android.material.bottomsheet.BottomSheetBehavior.* import com.google.android.material.bottomsheet.BottomSheetBehavior.*
import kotlinx.android.synthetic.main.sliding_music_panel_layout.*
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
companion object { companion object {
val TAG: String = AbsSlidingMusicPanelActivity::class.java.simpleName val TAG: String = AbsSlidingMusicPanelActivity::class.java.simpleName
var fromNotification: Boolean = false
} }
protected val libraryViewModel by viewModel<LibraryViewModel>() protected val libraryViewModel by viewModel<LibraryViewModel>()
@ -75,16 +76,17 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
private var lightStatusBar: Boolean = false private var lightStatusBar: Boolean = false
private var lightNavigationBar: Boolean = false private var lightNavigationBar: Boolean = false
private var paletteColor: Int = Color.WHITE private var paletteColor: Int = Color.WHITE
protected abstract fun createContentView(): View protected abstract fun createContentView(): SlidingMusicPanelLayoutBinding
private val panelState: Int private val panelState: Int
get() = bottomSheetBehavior.state get() = bottomSheetBehavior.state
private lateinit var binding: SlidingMusicPanelLayoutBinding
private val bottomSheetCallbackList = object : BottomSheetCallback() { private val bottomSheetCallbackList = object : BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) { override fun onSlide(bottomSheet: View, slideOffset: Float) {
setMiniPlayerAlphaProgress(slideOffset) setMiniPlayerAlphaProgress(slideOffset)
dimBackground.show() binding.dimBackground.show()
dimBackground.alpha = slideOffset binding.dimBackground.alpha = slideOffset
} }
override fun onStateChanged(bottomSheet: View, newState: Int) { override fun onStateChanged(bottomSheet: View, newState: Int) {
@ -94,9 +96,17 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
} }
STATE_COLLAPSED -> { STATE_COLLAPSED -> {
onPanelCollapsed() onPanelCollapsed()
dimBackground.hide() binding.dimBackground.hide()
if (fromNotification) {
hideBottomBar(MusicPlayerRemote.playingQueue.isEmpty())
fromNotification = false
}
}
STATE_SETTLING, STATE_DRAGGING -> {
if (fromNotification) {
getBottomNavigationView().isVisible = true
}
} }
else -> { else -> {
println("Do something") println("Do something")
} }
@ -108,23 +118,25 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(createContentView()) binding = createContentView()
setContentView(binding.root)
chooseFragmentForTheme() chooseFragmentForTheme()
setupSlidingUpPanel() setupSlidingUpPanel()
setupBottomSheet() setupBottomSheet()
updateColor() updateColor()
val themeColor = resolveColor(android.R.attr.windowBackground, Color.GRAY) val themeColor = resolveColor(android.R.attr.windowBackground, Color.GRAY)
dimBackground.setBackgroundColor(ColorUtil.withAlpha(themeColor, 0.5f)) binding.dimBackground.setBackgroundColor(ColorUtil.withAlpha(themeColor, 0.5f))
dimBackground.setOnClickListener { binding.dimBackground.setOnClickListener {
println("dimBackground") println("dimBackground")
collapsePanel() collapsePanel()
} }
} }
private fun setupBottomSheet() { private fun setupBottomSheet() {
bottomSheetBehavior = from(slidingPanel) as RetroBottomSheetBehavior bottomSheetBehavior = from(binding.slidingPanel) as RetroBottomSheetBehavior
bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallbackList) bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallbackList)
bottomSheetBehavior.maxWidth = resources.displayMetrics.widthPixels
} }
override fun onResume() { override fun onResume() {
@ -142,14 +154,13 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
bottomSheetBehavior.removeBottomSheetCallback(bottomSheetCallbackList) bottomSheetBehavior.removeBottomSheetCallback(bottomSheetCallbackList)
} }
@SuppressLint("InflateParams") protected fun wrapSlidingMusicPanel(): SlidingMusicPanelLayoutBinding {
protected fun wrapSlidingMusicPanel(): View { val slidingMusicPanelLayoutBinding =
val slidingMusicPanelLayout = SlidingMusicPanelLayoutBinding.inflate(layoutInflater)
layoutInflater.inflate(R.layout.sliding_music_panel_layout, null)
val contentContainer: ViewGroup = val contentContainer: ViewGroup =
slidingMusicPanelLayout.findViewById(R.id.mainContentFrame) slidingMusicPanelLayoutBinding.mainContentFrame
layoutInflater.inflate(R.layout.activity_main_content, contentContainer) ActivityMainContentBinding.inflate(layoutInflater, contentContainer, true)
return slidingMusicPanelLayout return slidingMusicPanelLayoutBinding
} }
fun collapsePanel() { fun collapsePanel() {
@ -166,8 +177,8 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
val alpha = 1 - progress val alpha = 1 - progress
miniPlayerFragment?.view?.alpha = alpha miniPlayerFragment?.view?.alpha = alpha
miniPlayerFragment?.view?.visibility = if (alpha == 0f) View.GONE else View.VISIBLE miniPlayerFragment?.view?.visibility = if (alpha == 0f) View.GONE else View.VISIBLE
bottomNavigationView.translationY = progress * 500 binding.bottomNavigationView.translationY = progress * 500
bottomNavigationView.alpha = alpha binding.bottomNavigationView.alpha = alpha
} }
open fun onPanelCollapsed() { open fun onPanelCollapsed() {
@ -183,14 +194,14 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
} }
private fun setupSlidingUpPanel() { private fun setupSlidingUpPanel() {
slidingPanel.viewTreeObserver.addOnGlobalLayoutListener(object : binding.slidingPanel.viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener { ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() { override fun onGlobalLayout() {
slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this) binding.slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this)
if (nowPlayingScreen != Peak) { if (nowPlayingScreen != Peak) {
val params = slidingPanel.layoutParams as ViewGroup.LayoutParams val params = binding.slidingPanel.layoutParams as ViewGroup.LayoutParams
params.height = ViewGroup.LayoutParams.MATCH_PARENT params.height = ViewGroup.LayoutParams.MATCH_PARENT
slidingPanel.layoutParams = params binding.slidingPanel.layoutParams = params
} }
when (panelState) { when (panelState) {
STATE_EXPANDED -> onPanelExpanded() STATE_EXPANDED -> onPanelExpanded()
@ -204,16 +215,16 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
} }
fun getBottomNavigationView(): BottomNavigationBarTinted { fun getBottomNavigationView(): BottomNavigationBarTinted {
return bottomNavigationView return binding.bottomNavigationView
} }
override fun onServiceConnected() { override fun onServiceConnected() {
super.onServiceConnected() super.onServiceConnected()
if (MusicPlayerRemote.playingQueue.isNotEmpty()) { if (MusicPlayerRemote.playingQueue.isNotEmpty()) {
slidingPanel.viewTreeObserver.addOnGlobalLayoutListener(object : binding.slidingPanel.viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener { ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() { override fun onGlobalLayout() {
slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this) binding.slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this)
hideBottomBar(false) hideBottomBar(false)
} }
}) })
@ -305,16 +316,17 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
} }
fun updateTabs() { fun updateTabs() {
bottomNavigationView.menu.clear() binding.bottomNavigationView.menu.clear()
val currentTabs: List<CategoryInfo> = PreferenceUtil.libraryCategory val currentTabs: List<CategoryInfo> = PreferenceUtil.libraryCategory
for (tab in currentTabs) { for (tab in currentTabs) {
if (tab.visible) { if (tab.visible) {
val menu = tab.category val menu = tab.category
bottomNavigationView.menu.add(0, menu.id, 0, menu.stringRes).setIcon(menu.icon) binding.bottomNavigationView.menu.add(0, menu.id, 0, menu.stringRes)
.setIcon(menu.icon)
} }
} }
if (bottomNavigationView.menu.size() == 1) { if (binding.bottomNavigationView.menu.size() == 1) {
bottomNavigationView.hide() binding.bottomNavigationView.hide()
} }
} }
@ -326,28 +338,34 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
} }
fun setBottomBarVisibility(visible: Boolean) { fun setBottomBarVisibility(visible: Boolean) {
bottomNavigationView.isVisible = visible binding.bottomNavigationView.isVisible = visible
hideBottomBar(MusicPlayerRemote.playingQueue.isEmpty()) hideBottomBar(MusicPlayerRemote.playingQueue.isEmpty())
} }
private fun hideBottomBar(hide: Boolean) { private fun hideBottomBar(hide: Boolean) {
val heightOfBar = dip(R.dimen.mini_player_height) val heightOfBar =
val heightOfBarWithTabs = heightOfBar * 2 if (MusicPlayerRemote.isCasting) dip(R.dimen.cast_mini_player_height) else dip(R.dimen.mini_player_height)
val isVisible = bottomNavigationView.isVisible val heightOfBarWithTabs =
if (MusicPlayerRemote.isCasting) dip(R.dimen.mini_cast_player_height_expanded) else dip(
R.dimen.mini_player_height_expanded
)
val isVisible = binding.bottomNavigationView.isVisible
if (hide) { if (hide) {
bottomSheetBehavior.isHideable = true bottomSheetBehavior.isHideable = true
bottomSheetBehavior.peekHeight = 0 bottomSheetBehavior.peekHeight = 0
ViewCompat.setElevation(slidingPanel, 0f) ViewCompat.setElevation(binding.slidingPanel, 0f)
ViewCompat.setElevation(bottomNavigationView, 10f) ViewCompat.setElevation(binding.bottomNavigationView, 10f)
collapsePanel() collapsePanel()
} else { } else {
if (MusicPlayerRemote.playingQueue.isNotEmpty()) { if (MusicPlayerRemote.playingQueue.isNotEmpty()) {
bottomSheetBehavior.isHideable = false bottomSheetBehavior.isHideable = false
ViewCompat.setElevation(slidingPanel, 10f) ViewCompat.setElevation(binding.slidingPanel, 10f)
ViewCompat.setElevation(bottomNavigationView, 10f) ViewCompat.setElevation(binding.bottomNavigationView, 10f)
if (isVisible) { if (isVisible) {
println("List") println("List")
bottomSheetBehavior.peekHeight = heightOfBarWithTabs - 22 if (bottomSheetBehavior.state != STATE_EXPANDED)
getBottomNavigationView().translateYAnimate(0F)
bottomSheetBehavior.peekHeightAnimate(heightOfBarWithTabs)
} else { } else {
println("Details") println("Details")
bottomSheetBehavior.peekHeight = heightOfBar bottomSheetBehavior.peekHeight = heightOfBar
@ -356,6 +374,11 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
} }
} }
fun setAllowDragging(allowDragging: Boolean) {
bottomSheetBehavior.setAllowDragging(allowDragging)
hideBottomBar(false)
}
private fun chooseFragmentForTheme() { private fun chooseFragmentForTheme() {
nowPlayingScreen = PreferenceUtil.nowPlayingScreen nowPlayingScreen = PreferenceUtil.nowPlayingScreen

View file

@ -15,6 +15,7 @@
package code.name.monkey.retromusic.activities.base package code.name.monkey.retromusic.activities.base
import android.content.Context import android.content.Context
import android.content.res.Resources
import android.graphics.Color import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
@ -23,12 +24,12 @@ import android.view.View
import android.view.WindowManager import android.view.WindowManager
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode import androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode
import androidx.core.os.ConfigurationCompat
import code.name.monkey.appthemehelper.ATH import code.name.monkey.appthemehelper.ATH
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.common.ATHToolbarActivity import code.name.monkey.appthemehelper.common.ATHToolbarActivity
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialDialogsUtil
import code.name.monkey.appthemehelper.util.VersionUtils import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.LanguageContextWrapper import code.name.monkey.retromusic.LanguageContextWrapper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
@ -48,7 +49,7 @@ abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable {
setImmersiveFullscreen() setImmersiveFullscreen()
registerSystemUiVisibility() registerSystemUiVisibility()
toggleScreenOn() toggleScreenOn()
MaterialDialogsUtil.updateMaterialDialogsThemeSingleton(this) //MaterialDialogsUtil.updateMaterialDialogsThemeSingleton(this)
} }
private fun updateTheme() { private fun updateTheme() {
@ -213,8 +214,12 @@ abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable {
override fun attachBaseContext(newBase: Context?) { override fun attachBaseContext(newBase: Context?) {
val code = PreferenceUtil.languageCode val code = PreferenceUtil.languageCode
if (code != "auto") { val locale = if (code == "auto") {
super.attachBaseContext(LanguageContextWrapper.wrap(newBase, Locale(code))) // Get the device default locale
} else super.attachBaseContext(newBase) ConfigurationCompat.getLocales(Resources.getSystem().configuration)[0]
} else {
Locale.forLanguageTag(code)
}
super.attachBaseContext(LanguageContextWrapper.wrap(newBase, locale))
} }
} }

View file

@ -41,18 +41,16 @@ import code.name.monkey.retromusic.activities.bugreport.model.Report
import code.name.monkey.retromusic.activities.bugreport.model.github.ExtraInfo import code.name.monkey.retromusic.activities.bugreport.model.github.ExtraInfo
import code.name.monkey.retromusic.activities.bugreport.model.github.GithubLogin import code.name.monkey.retromusic.activities.bugreport.model.github.GithubLogin
import code.name.monkey.retromusic.activities.bugreport.model.github.GithubTarget import code.name.monkey.retromusic.activities.bugreport.model.github.GithubTarget
import code.name.monkey.retromusic.databinding.ActivityBugReportBinding
import code.name.monkey.retromusic.misc.DialogAsyncTask import code.name.monkey.retromusic.misc.DialogAsyncTask
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import java.io.IOException
import kotlinx.android.synthetic.main.activity_bug_report.*
import kotlinx.android.synthetic.main.bug_report_card_device_info.*
import kotlinx.android.synthetic.main.bug_report_card_report.*
import org.eclipse.egit.github.core.Issue import org.eclipse.egit.github.core.Issue
import org.eclipse.egit.github.core.client.GitHubClient import org.eclipse.egit.github.core.client.GitHubClient
import org.eclipse.egit.github.core.client.RequestException import org.eclipse.egit.github.core.client.RequestException
import org.eclipse.egit.github.core.service.IssueService import org.eclipse.egit.github.core.service.IssueService
import java.io.IOException
private const val RESULT_SUCCESS = "RESULT_OK" private const val RESULT_SUCCESS = "RESULT_OK"
private const val RESULT_BAD_CREDENTIALS = "RESULT_BAD_CREDENTIALS" private const val RESULT_BAD_CREDENTIALS = "RESULT_BAD_CREDENTIALS"
@ -72,12 +70,14 @@ private annotation class Result
open class BugReportActivity : AbsThemeActivity() { open class BugReportActivity : AbsThemeActivity() {
private lateinit var binding: ActivityBugReportBinding
private var deviceInfo: DeviceInfo? = null private var deviceInfo: DeviceInfo? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar() setDrawUnderStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_bug_report) binding = ActivityBugReportBinding.inflate(layoutInflater)
setContentView(binding.root)
setStatusbarColorAuto() setStatusbarColorAuto()
setNavigationbarColorAuto() setNavigationbarColorAuto()
setTaskDescriptionColorAuto() setTaskDescriptionColorAuto()
@ -87,50 +87,50 @@ open class BugReportActivity : AbsThemeActivity() {
if (TextUtils.isEmpty(title)) setTitle(R.string.report_an_issue) if (TextUtils.isEmpty(title)) setTitle(R.string.report_an_issue)
deviceInfo = DeviceInfo(this) deviceInfo = DeviceInfo(this)
airTextDeviceInfo.text = deviceInfo.toString() binding.cardDeviceInfo.airTextDeviceInfo.text = deviceInfo.toString()
} }
private fun initViews() { private fun initViews() {
val accentColor = ThemeStore.accentColor(this) val accentColor = ThemeStore.accentColor(this)
val primaryColor = ATHUtil.resolveColor(this, R.attr.colorSurface) val primaryColor = ATHUtil.resolveColor(this, R.attr.colorSurface)
toolbar.setBackgroundColor(primaryColor) binding.toolbar.setBackgroundColor(primaryColor)
setSupportActionBar(toolbar) setSupportActionBar(binding.toolbar)
ToolbarContentTintHelper.colorBackButton(toolbar) ToolbarContentTintHelper.colorBackButton(binding.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
TintHelper.setTintAuto(optionUseAccount, accentColor, false) TintHelper.setTintAuto(binding.cardReport.optionUseAccount, accentColor, false)
optionUseAccount?.setOnClickListener { binding.cardReport.optionUseAccount.setOnClickListener {
inputTitle.isEnabled = true binding.cardReport.inputTitle.isEnabled = true
inputDescription.isEnabled = true binding.cardReport.inputDescription.isEnabled = true
inputUsername.isEnabled = true binding.cardReport.inputUsername.isEnabled = true
inputPassword.isEnabled = true binding.cardReport.inputPassword.isEnabled = true
optionAnonymous.isChecked = false binding.cardReport.optionAnonymous.isChecked = false
sendFab.hide(object : FloatingActionButton.OnVisibilityChangedListener() { binding.sendFab.hide(object : FloatingActionButton.OnVisibilityChangedListener() {
override fun onHidden(fab: FloatingActionButton?) { override fun onHidden(fab: FloatingActionButton?) {
super.onHidden(fab) super.onHidden(fab)
sendFab.setImageResource(R.drawable.ic_send) binding.sendFab.setImageResource(R.drawable.ic_send)
sendFab.show() binding.sendFab.show()
} }
}) })
} }
TintHelper.setTintAuto(optionAnonymous, accentColor, false) TintHelper.setTintAuto(binding.cardReport.optionAnonymous, accentColor, false)
optionAnonymous.setOnClickListener { binding.cardReport.optionAnonymous.setOnClickListener {
inputTitle.isEnabled = false binding.cardReport.inputTitle.isEnabled = false
inputDescription.isEnabled = false binding.cardReport.inputDescription.isEnabled = false
inputUsername.isEnabled = false binding.cardReport.inputUsername.isEnabled = false
inputPassword.isEnabled = false binding.cardReport.inputPassword.isEnabled = false
optionUseAccount.isChecked = false binding.cardReport.optionUseAccount.isChecked = false
sendFab.hide(object : FloatingActionButton.OnVisibilityChangedListener() { binding.sendFab.hide(object : FloatingActionButton.OnVisibilityChangedListener() {
override fun onHidden(fab: FloatingActionButton?) { override fun onHidden(fab: FloatingActionButton?) {
super.onHidden(fab) super.onHidden(fab)
sendFab.setImageResource(R.drawable.ic_open_in_browser) binding.sendFab.setImageResource(R.drawable.ic_open_in_browser)
sendFab.show() binding.sendFab.show()
} }
}) })
} }
inputPassword.setOnEditorActionListener { _, actionId, _ -> binding.cardReport.inputPassword.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_SEND) { if (actionId == EditorInfo.IME_ACTION_SEND) {
reportIssue() reportIssue()
return@setOnEditorActionListener true return@setOnEditorActionListener true
@ -138,22 +138,22 @@ open class BugReportActivity : AbsThemeActivity() {
false false
} }
airTextDeviceInfo.setOnClickListener { copyDeviceInfoToClipBoard() } binding.cardDeviceInfo.airTextDeviceInfo.setOnClickListener { copyDeviceInfoToClipBoard() }
TintHelper.setTintAuto(sendFab, accentColor, true) TintHelper.setTintAuto(binding.sendFab, accentColor, true)
sendFab.setOnClickListener { reportIssue() } binding.sendFab.setOnClickListener { reportIssue() }
MaterialUtil.setTint(inputLayoutTitle, false) MaterialUtil.setTint(binding.cardReport.inputLayoutTitle, false)
MaterialUtil.setTint(inputLayoutDescription, false) MaterialUtil.setTint(binding.cardReport.inputLayoutDescription, false)
MaterialUtil.setTint(inputLayoutUsername, false) MaterialUtil.setTint(binding.cardReport.inputLayoutUsername, false)
MaterialUtil.setTint(inputLayoutPassword, false) MaterialUtil.setTint(binding.cardReport.inputLayoutPassword, false)
} }
private fun reportIssue() { private fun reportIssue() {
if (optionUseAccount.isChecked) { if (binding.cardReport.optionUseAccount.isChecked) {
if (!validateInput()) return if (!validateInput()) return
val username = inputUsername.text.toString() val username = binding.cardReport.inputUsername.text.toString()
val password = inputPassword.text.toString() val password = binding.cardReport.inputPassword.text.toString()
sendBugReport(GithubLogin(username, password)) sendBugReport(GithubLogin(username, password))
} else { } else {
copyDeviceInfoToClipBoard() copyDeviceInfoToClipBoard()
@ -179,34 +179,34 @@ open class BugReportActivity : AbsThemeActivity() {
private fun validateInput(): Boolean { private fun validateInput(): Boolean {
var hasErrors = false var hasErrors = false
if (optionUseAccount.isChecked) { if (binding.cardReport.optionUseAccount.isChecked) {
if (TextUtils.isEmpty(inputUsername.text)) { if (TextUtils.isEmpty(binding.cardReport.inputUsername.text)) {
setError(inputLayoutUsername, R.string.bug_report_no_username) setError(binding.cardReport.inputLayoutUsername, R.string.bug_report_no_username)
hasErrors = true hasErrors = true
} else { } else {
removeError(inputLayoutUsername) removeError(binding.cardReport.inputLayoutUsername)
} }
if (TextUtils.isEmpty(inputPassword.text)) { if (TextUtils.isEmpty(binding.cardReport.inputPassword.text)) {
setError(inputLayoutPassword, R.string.bug_report_no_password) setError(binding.cardReport.inputLayoutPassword, R.string.bug_report_no_password)
hasErrors = true hasErrors = true
} else { } else {
removeError(inputLayoutPassword) removeError(binding.cardReport.inputLayoutPassword)
} }
} }
if (TextUtils.isEmpty(inputTitle.text)) { if (TextUtils.isEmpty(binding.cardReport.inputTitle.text)) {
setError(inputLayoutTitle, R.string.bug_report_no_title) setError(binding.cardReport.inputLayoutTitle, R.string.bug_report_no_title)
hasErrors = true hasErrors = true
} else { } else {
removeError(inputLayoutTitle) removeError(binding.cardReport.inputLayoutTitle)
} }
if (TextUtils.isEmpty(inputDescription.text)) { if (TextUtils.isEmpty(binding.cardReport.inputDescription.text)) {
setError(inputLayoutDescription, R.string.bug_report_no_description) setError(binding.cardReport.inputLayoutDescription, R.string.bug_report_no_description)
hasErrors = true hasErrors = true
} else { } else {
removeError(inputLayoutDescription) removeError(binding.cardReport.inputLayoutDescription)
} }
return !hasErrors return !hasErrors
@ -223,8 +223,8 @@ open class BugReportActivity : AbsThemeActivity() {
private fun sendBugReport(login: GithubLogin) { private fun sendBugReport(login: GithubLogin) {
if (!validateInput()) return if (!validateInput()) return
val bugTitle = inputTitle.text.toString() val bugTitle = binding.cardReport.inputTitle.text.toString()
val bugDescription = inputDescription.text.toString() val bugDescription = binding.cardReport.inputDescription.text.toString()
val extraInfo = ExtraInfo() val extraInfo = ExtraInfo()
onSaveExtraInfo() onSaveExtraInfo()

View file

@ -5,11 +5,14 @@ import android.content.Context;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Build; import android.os.Build;
import androidx.annotation.IntRange; import androidx.annotation.IntRange;
import code.name.monkey.retromusic.util.PreferenceUtil;
import java.util.Arrays; import java.util.Arrays;
import java.util.Locale; import java.util.Locale;
import code.name.monkey.retromusic.util.PreferenceUtil;
public class DeviceInfo { public class DeviceInfo {
@SuppressLint("NewApi") @SuppressLint("NewApi")

View file

@ -16,11 +16,14 @@ package code.name.monkey.retromusic.activities.saf;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import code.name.monkey.retromusic.R;
import com.heinrichreimersoftware.materialintro.app.IntroActivity; import com.heinrichreimersoftware.materialintro.app.IntroActivity;
import com.heinrichreimersoftware.materialintro.slide.SimpleSlide; import com.heinrichreimersoftware.materialintro.slide.SimpleSlide;
import code.name.monkey.retromusic.R;
/** Created by hemanths on 2019-07-31. */ /** Created by hemanths on 2019-07-31. */
public class SAFGuideActivity extends IntroActivity { public class SAFGuideActivity extends IntroActivity {

View file

@ -22,10 +22,12 @@ import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View
import android.view.animation.OvershootInterpolator import android.view.animation.OvershootInterpolator
import android.widget.ImageView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.viewbinding.ViewBinding
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.appthemehelper.util.TintHelper
@ -41,7 +43,6 @@ import code.name.monkey.retromusic.util.RetroUtil
import code.name.monkey.retromusic.util.SAFUtil import code.name.monkey.retromusic.util.SAFUtil
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.android.synthetic.main.activity_album_tag_editor.*
import org.jaudiotagger.audio.AudioFile import org.jaudiotagger.audio.AudioFile
import org.jaudiotagger.audio.AudioFileIO import org.jaudiotagger.audio.AudioFileIO
import org.jaudiotagger.tag.FieldKey import org.jaudiotagger.tag.FieldKey
@ -49,7 +50,8 @@ import org.koin.android.ext.android.inject
import java.io.File import java.io.File
import java.util.* import java.util.*
abstract class AbsTagEditorActivity : AbsBaseActivity() { abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
abstract val editorImage: ImageView?
val repository by inject<Repository>() val repository by inject<Repository>()
lateinit var saveFab: MaterialButton lateinit var saveFab: MaterialButton
@ -62,7 +64,11 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
private val currentSongPath: String? = null private val currentSongPath: String? = null
private var savedTags: Map<FieldKey, String>? = null private var savedTags: Map<FieldKey, String>? = null
private var savedArtworkInfo: ArtworkInfo? = null private var savedArtworkInfo: ArtworkInfo? = null
protected abstract val contentViewLayout: Int private var _binding: VB? = null
protected val binding: VB get() = _binding!!
abstract val bindingInflater: (LayoutInflater) -> VB
protected abstract fun loadImageFromFile(selectedFile: Uri?) protected abstract fun loadImageFromFile(selectedFile: Uri?)
protected val show: AlertDialog protected val show: AlertDialog
@ -187,7 +193,8 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(contentViewLayout) _binding = bindingInflater.invoke(layoutInflater)
setContentView(binding.root)
setStatusbarColorAuto() setStatusbarColorAuto()
setNavigationbarColorAuto() setNavigationbarColorAuto()
setTaskDescriptionColorAuto() setTaskDescriptionColorAuto()
@ -284,10 +291,6 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
protected fun setNoImageMode() { protected fun setNoImageMode() {
isInNoImageMode = true isInNoImageMode = true
imageContainer?.visibility = View.GONE
editorImage?.visibility = View.GONE
editorImage?.isEnabled = false
setColors( setColors(
intent.getIntExtra( intent.getIntExtra(
EXTRA_PALETTE, EXTRA_PALETTE,
@ -296,6 +299,7 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
) )
} }
protected fun dataChanged() { protected fun dataChanged() {
showFab() showFab()
} }
@ -314,9 +318,9 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
protected fun setImageBitmap(bitmap: Bitmap?, bgColor: Int) { protected fun setImageBitmap(bitmap: Bitmap?, bgColor: Int) {
if (bitmap == null) { if (bitmap == null) {
editorImage.setImageResource(drawable.default_audio_art) editorImage?.setImageResource(drawable.default_audio_art)
} else { } else {
editorImage.setImageBitmap(bitmap) editorImage?.setImageBitmap(bitmap)
} }
setColors(bgColor) setColors(bgColor)
} }

View file

@ -25,30 +25,31 @@ import android.os.Bundle
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.transition.Slide import android.transition.Slide
import android.view.LayoutInflater
import android.widget.ImageView
import android.widget.Toast import android.widget.Toast
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.MaterialUtil import code.name.monkey.appthemehelper.util.MaterialUtil
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.ActivityAlbumTagEditorBinding
import code.name.monkey.retromusic.extensions.appHandleColor import code.name.monkey.retromusic.extensions.appHandleColor
import code.name.monkey.retromusic.glide.palette.BitmapPaletteTranscoder import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
import code.name.monkey.retromusic.model.ArtworkInfo import code.name.monkey.retromusic.model.ArtworkInfo
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.ImageUtil import code.name.monkey.retromusic.util.ImageUtil
import code.name.monkey.retromusic.util.RetroColorUtil.generatePalette import code.name.monkey.retromusic.util.RetroColorUtil.generatePalette
import code.name.monkey.retromusic.util.RetroColorUtil.getColor import code.name.monkey.retromusic.util.RetroColorUtil.getColor
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.animation.GlideAnimation import com.bumptech.glide.request.target.ImageViewTarget
import com.bumptech.glide.request.target.SimpleTarget import com.bumptech.glide.request.transition.Transition
import java.util.*
import kotlinx.android.synthetic.main.activity_album_tag_editor.*
import org.jaudiotagger.tag.FieldKey import org.jaudiotagger.tag.FieldKey
import java.util.*
class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher { class AlbumTagEditorActivity : AbsTagEditorActivity<ActivityAlbumTagEditorBinding>(), TextWatcher {
override val contentViewLayout: Int override val bindingInflater: (LayoutInflater) -> ActivityAlbumTagEditorBinding =
get() = R.layout.activity_album_tag_editor ActivityAlbumTagEditorBinding::inflate
private fun windowEnterTransition() { private fun windowEnterTransition() {
val slide = Slide() val slide = Slide()
@ -62,20 +63,20 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
override fun loadImageFromFile(selectedFile: Uri?) { override fun loadImageFromFile(selectedFile: Uri?) {
Glide.with(this@AlbumTagEditorActivity).load(selectedFile).asBitmap()
.transcode(BitmapPaletteTranscoder(this), BitmapPaletteWrapper::class.java) GlideApp.with(this@AlbumTagEditorActivity).asBitmapPalette().load(selectedFile)
.diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true) .diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true)
.into(object : SimpleTarget<BitmapPaletteWrapper>() { .into(object : ImageViewTarget<BitmapPaletteWrapper>(binding.editorImage) {
override fun onResourceReady( override fun onResourceReady(
resource: BitmapPaletteWrapper?, resource: BitmapPaletteWrapper,
glideAnimation: GlideAnimation<in BitmapPaletteWrapper>? transition: Transition<in BitmapPaletteWrapper>?
) { ) {
getColor(resource?.palette, Color.TRANSPARENT) getColor(resource.palette, Color.TRANSPARENT)
albumArtBitmap = resource?.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) } albumArtBitmap = resource.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) }
setImageBitmap( setImageBitmap(
albumArtBitmap, albumArtBitmap,
getColor( getColor(
resource?.palette, resource.palette,
ATHUtil.resolveColor( ATHUtil.resolveColor(
this@AlbumTagEditorActivity, this@AlbumTagEditorActivity,
R.attr.defaultFooterColor R.attr.defaultFooterColor
@ -87,11 +88,13 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
setResult(Activity.RESULT_OK) setResult(Activity.RESULT_OK)
} }
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) { override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(e, errorDrawable) super.onLoadFailed(errorDrawable)
Toast.makeText(this@AlbumTagEditorActivity, e.toString(), Toast.LENGTH_LONG) Toast.makeText(this@AlbumTagEditorActivity, "Load Failed", Toast.LENGTH_LONG)
.show() .show()
} }
override fun setResource(resource: BitmapPaletteWrapper?) {}
}) })
} }
@ -99,15 +102,15 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
private var deleteAlbumArt: Boolean = false private var deleteAlbumArt: Boolean = false
private fun setupToolbar() { private fun setupToolbar() {
toolbar.setBackgroundColor(ATHUtil.resolveColor(this, R.attr.colorSurface)) binding.toolbar.setBackgroundColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
setSupportActionBar(toolbar) setSupportActionBar(binding.toolbar)
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar() setDrawUnderStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
window.sharedElementsUseOverlay = true window.sharedElementsUseOverlay = true
imageContainer?.transitionName = getString(R.string.transition_album_art) binding.imageContainer.transitionName = getString(R.string.transition_album_art)
windowEnterTransition() windowEnterTransition()
setUpViews() setUpViews()
setupToolbar() setupToolbar()
@ -116,22 +119,23 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
private fun setUpViews() { private fun setUpViews() {
fillViewsWithFileTags() fillViewsWithFileTags()
MaterialUtil.setTint(yearContainer, false) MaterialUtil.setTint(binding.yearContainer, false)
MaterialUtil.setTint(genreContainer, false) MaterialUtil.setTint(binding.genreContainer, false)
MaterialUtil.setTint(albumTitleContainer, false) MaterialUtil.setTint(binding.albumTitleContainer, false)
MaterialUtil.setTint(albumArtistContainer, false) MaterialUtil.setTint(binding.albumArtistContainer, false)
albumText.appHandleColor().addTextChangedListener(this) binding.albumText.appHandleColor().addTextChangedListener(this)
albumArtistText.appHandleColor().addTextChangedListener(this) binding.albumArtistText.appHandleColor().addTextChangedListener(this)
genreTitle.appHandleColor().addTextChangedListener(this) binding.genreTitle.appHandleColor().addTextChangedListener(this)
yearTitle.appHandleColor().addTextChangedListener(this) binding.yearTitle.appHandleColor().addTextChangedListener(this)
} }
private fun fillViewsWithFileTags() { private fun fillViewsWithFileTags() {
albumText.setText(albumTitle) binding.albumText.setText(albumTitle)
albumArtistText.setText(albumArtistName) binding.albumArtistText.setText(albumArtistName)
genreTitle.setText(genreName) binding.genreTitle.setText(genreName)
yearTitle.setText(songYear) binding.yearTitle.setText(songYear)
println(albumTitle + albumArtistName)
} }
override fun loadCurrentImage() { override fun loadCurrentImage() {
@ -155,7 +159,7 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
} }
override fun searchImageOnWeb() { override fun searchImageOnWeb() {
searchWebFor(albumText.text.toString(), albumArtistText.text.toString()) searchWebFor(binding.albumText.text.toString(), binding.albumArtistText.text.toString())
} }
override fun deleteImage() { override fun deleteImage() {
@ -169,12 +173,12 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
override fun save() { override fun save() {
val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java) val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java)
fieldKeyValueMap[FieldKey.ALBUM] = albumText.text.toString() fieldKeyValueMap[FieldKey.ALBUM] = binding.albumText.text.toString()
// android seems not to recognize album_artist field so we additionally write the normal artist field // android seems not to recognize album_artist field so we additionally write the normal artist field
fieldKeyValueMap[FieldKey.ARTIST] = albumArtistText.text.toString() fieldKeyValueMap[FieldKey.ARTIST] = binding.albumArtistText.text.toString()
fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = albumArtistText.text.toString() fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = binding.albumArtistText.text.toString()
fieldKeyValueMap[FieldKey.GENRE] = genreTitle.text.toString() fieldKeyValueMap[FieldKey.GENRE] = binding.genreTitle.text.toString()
fieldKeyValueMap[FieldKey.YEAR] = yearTitle.text.toString() fieldKeyValueMap[FieldKey.YEAR] = binding.yearTitle.text.toString()
writeValuesToFiles( writeValuesToFiles(
fieldKeyValueMap, fieldKeyValueMap,
@ -206,6 +210,10 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
saveFab.backgroundTintList = ColorStateList.valueOf(color) saveFab.backgroundTintList = ColorStateList.valueOf(color)
} }
override val editorImage: ImageView
get() = binding.editorImage
companion object { companion object {
val TAG: String = AlbumTagEditorActivity::class.java.simpleName val TAG: String = AlbumTagEditorActivity::class.java.simpleName

View file

@ -14,91 +14,98 @@
*/ */
package code.name.monkey.retromusic.activities.tageditor package code.name.monkey.retromusic.activities.tageditor
import android.annotation.SuppressLint
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.view.LayoutInflater
import android.widget.ImageView
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.MaterialUtil import code.name.monkey.appthemehelper.util.MaterialUtil
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.ActivitySongTagEditorBinding
import code.name.monkey.retromusic.extensions.appHandleColor import code.name.monkey.retromusic.extensions.appHandleColor
import code.name.monkey.retromusic.repository.SongRepository import code.name.monkey.retromusic.repository.SongRepository
import kotlinx.android.synthetic.main.activity_song_tag_editor.*
import org.jaudiotagger.tag.FieldKey import org.jaudiotagger.tag.FieldKey
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import java.util.* import java.util.*
class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher { class SongTagEditorActivity : AbsTagEditorActivity<ActivitySongTagEditorBinding>(), TextWatcher {
override val bindingInflater: (LayoutInflater) -> ActivitySongTagEditorBinding =
ActivitySongTagEditorBinding::inflate
override val contentViewLayout: Int
get() = R.layout.activity_song_tag_editor
private val songRepository by inject<SongRepository>() private val songRepository by inject<SongRepository>()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setNoImageMode()
setUpViews() setUpViews()
toolbar.setBackgroundColor(ATHUtil.resolveColor(this, R.attr.colorSurface)) setNoImageMode()
setSupportActionBar(toolbar) binding.toolbar.setBackgroundColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
setSupportActionBar(binding.toolbar)
} }
@SuppressLint("ClickableViewAccessibility")
private fun setUpViews() { private fun setUpViews() {
fillViewsWithFileTags() fillViewsWithFileTags()
MaterialUtil.setTint(songTextContainer, false) MaterialUtil.setTint(binding.songTextContainer, false)
MaterialUtil.setTint(composerContainer, false) MaterialUtil.setTint(binding.composerContainer, false)
MaterialUtil.setTint(albumTextContainer, false) MaterialUtil.setTint(binding.albumTextContainer, false)
MaterialUtil.setTint(artistContainer, false) MaterialUtil.setTint(binding.artistContainer, false)
MaterialUtil.setTint(albumArtistContainer, false) MaterialUtil.setTint(binding.albumArtistContainer, false)
MaterialUtil.setTint(yearContainer, false) MaterialUtil.setTint(binding.yearContainer, false)
MaterialUtil.setTint(genreContainer, false) MaterialUtil.setTint(binding.genreContainer, false)
MaterialUtil.setTint(trackNumberContainer, false) MaterialUtil.setTint(binding.trackNumberContainer, false)
MaterialUtil.setTint(lyricsContainer, false) MaterialUtil.setTint(binding.lyricsContainer, false)
songText.appHandleColor().addTextChangedListener(this) binding.songText.appHandleColor().addTextChangedListener(this)
albumText.appHandleColor().addTextChangedListener(this) binding.albumText.appHandleColor().addTextChangedListener(this)
albumArtistText.appHandleColor().addTextChangedListener(this) binding.albumArtistText.appHandleColor().addTextChangedListener(this)
artistText.appHandleColor().addTextChangedListener(this) binding.artistText.appHandleColor().addTextChangedListener(this)
genreText.appHandleColor().addTextChangedListener(this) binding.genreText.appHandleColor().addTextChangedListener(this)
yearText.appHandleColor().addTextChangedListener(this) binding.yearText.appHandleColor().addTextChangedListener(this)
trackNumberText.appHandleColor().addTextChangedListener(this) binding.trackNumberText.appHandleColor().addTextChangedListener(this)
lyricsText.appHandleColor().addTextChangedListener(this) binding.lyricsText.appHandleColor().addTextChangedListener(this)
songComposerText.appHandleColor().addTextChangedListener(this) binding.songComposerText.appHandleColor().addTextChangedListener(this)
binding.lyricsText.setOnTouchListener { view, _ ->
view.parent.requestDisallowInterceptTouchEvent(true)
return@setOnTouchListener false
}
} }
private fun fillViewsWithFileTags() { private fun fillViewsWithFileTags() {
songText.setText(songTitle) binding.songText.setText(songTitle)
albumArtistText.setText(albumArtist) binding.albumArtistText.setText(albumArtist)
albumText.setText(albumTitle) binding.albumText.setText(albumTitle)
artistText.setText(artistName) binding.artistText.setText(artistName)
genreText.setText(genreName) binding.genreText.setText(genreName)
yearText.setText(songYear) binding.yearText.setText(songYear)
trackNumberText.setText(trackNumber) binding.trackNumberText.setText(trackNumber)
lyricsText.setText(lyrics) binding.lyricsText.setText(lyrics)
songComposerText.setText(composer) binding.songComposerText.setText(composer)
println(songTitle + songYear)
} }
override fun loadCurrentImage() { override fun loadCurrentImage() {}
}
override fun searchImageOnWeb() { override fun searchImageOnWeb() {}
}
override fun deleteImage() { override fun deleteImage() {}
}
override fun save() { override fun save() {
val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java) val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java)
fieldKeyValueMap[FieldKey.TITLE] = songText.text.toString() fieldKeyValueMap[FieldKey.TITLE] = binding.songText.text.toString()
fieldKeyValueMap[FieldKey.ALBUM] = albumText.text.toString() fieldKeyValueMap[FieldKey.ALBUM] = binding.albumText.text.toString()
fieldKeyValueMap[FieldKey.ARTIST] = artistText.text.toString() fieldKeyValueMap[FieldKey.ARTIST] = binding.artistText.text.toString()
fieldKeyValueMap[FieldKey.GENRE] = genreText.text.toString() fieldKeyValueMap[FieldKey.GENRE] = binding.genreText.text.toString()
fieldKeyValueMap[FieldKey.YEAR] = yearText.text.toString() fieldKeyValueMap[FieldKey.YEAR] = binding.yearText.text.toString()
fieldKeyValueMap[FieldKey.TRACK] = trackNumberText.text.toString() fieldKeyValueMap[FieldKey.TRACK] = binding.trackNumberText.text.toString()
fieldKeyValueMap[FieldKey.LYRICS] = lyricsText.text.toString() fieldKeyValueMap[FieldKey.LYRICS] = binding.lyricsText.text.toString()
fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = albumArtistText.text.toString() fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = binding.albumArtistText.text.toString()
fieldKeyValueMap[FieldKey.COMPOSER] = songComposerText.text.toString() fieldKeyValueMap[FieldKey.COMPOSER] = binding.songComposerText.text.toString()
writeValuesToFiles(fieldKeyValueMap, null) writeValuesToFiles(fieldKeyValueMap, null)
} }
@ -120,4 +127,7 @@ class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
companion object { companion object {
val TAG: String = SongTagEditorActivity::class.java.simpleName val TAG: String = SongTagEditorActivity::class.java.simpleName
} }
override val editorImage: ImageView?
get() = null
} }

View file

@ -10,7 +10,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.afollestad.materialdialogs.MaterialDialog; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.jaudiotagger.audio.AudioFile; import org.jaudiotagger.audio.AudioFile;
import org.jaudiotagger.audio.AudioFileIO; import org.jaudiotagger.audio.AudioFileIO;
@ -136,17 +136,17 @@ public class WriteTagsAsyncTask extends DialogAsyncTask<LoadingInfo, Integer, Li
@Override @Override
protected Dialog createDialog(@NonNull Context context) { protected Dialog createDialog(@NonNull Context context) {
return new MaterialDialog.Builder(context) return new MaterialAlertDialogBuilder(context)
.title(R.string.saving_changes) .setTitle(R.string.saving_changes)
.cancelable(false) .setCancelable(false)
.progress(false, 0) .setView(R.layout.loading)
.build(); .create();
} }
@Override @Override
protected void onProgressUpdate(@NonNull Dialog dialog, Integer... values) { protected void onProgressUpdate(@NonNull Dialog dialog, Integer... values) {
super.onProgressUpdate(dialog, values); super.onProgressUpdate(dialog, values);
((MaterialDialog) dialog).setMaxProgress(values[1]); // ((MaterialDialog) dialog).setMaxProgress(values[1]);
((MaterialDialog) dialog).setProgress(values[0]); // ((MaterialDialog) dialog).setProgress(values[0]);
} }
} }

View file

@ -22,15 +22,19 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.checkbox.MaterialCheckBox;
import java.util.List;
import code.name.monkey.appthemehelper.ThemeStore; import code.name.monkey.appthemehelper.ThemeStore;
import code.name.monkey.retromusic.R; import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.model.CategoryInfo; import code.name.monkey.retromusic.model.CategoryInfo;
import code.name.monkey.retromusic.util.SwipeAndDragHelper; import code.name.monkey.retromusic.util.SwipeAndDragHelper;
import com.google.android.material.checkbox.MaterialCheckBox;
import java.util.List;
public class CategoryInfoAdapter extends RecyclerView.Adapter<CategoryInfoAdapter.ViewHolder> public class CategoryInfoAdapter extends RecyclerView.Adapter<CategoryInfoAdapter.ViewHolder>
implements SwipeAndDragHelper.ActionCompletionContract { implements SwipeAndDragHelper.ActionCompletionContract {

View file

@ -17,13 +17,18 @@ package code.name.monkey.retromusic.adapter
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.ViewCompat import android.view.ViewOutlineProvider
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.interfaces.IGenreClickListener import code.name.monkey.retromusic.interfaces.IGenreClickListener
import code.name.monkey.retromusic.model.Genre import code.name.monkey.retromusic.model.Genre
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import java.util.* import java.util.*
/** /**
@ -36,6 +41,15 @@ class GenreAdapter(
private val mItemLayoutRes: Int, private val mItemLayoutRes: Int,
private val listener: IGenreClickListener private val listener: IGenreClickListener
) : RecyclerView.Adapter<GenreAdapter.ViewHolder>() { ) : RecyclerView.Adapter<GenreAdapter.ViewHolder>() {
init {
this.setHasStableIds(true)
}
override fun getItemId(position: Int): Long {
return dataSet[position].id
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(activity).inflate(mItemLayoutRes, parent, false)) return ViewHolder(LayoutInflater.from(activity).inflate(mItemLayoutRes, parent, false))
} }
@ -49,6 +63,28 @@ class GenreAdapter(
genre.songCount, genre.songCount,
if (genre.songCount > 1) activity.getString(R.string.songs) else activity.getString(R.string.song) if (genre.songCount > 1) activity.getString(R.string.songs) else activity.getString(R.string.song)
) )
loadGenreImage(genre, holder)
}
private fun loadGenreImage(genre: Genre, holder: GenreAdapter.ViewHolder) {
val genreSong = MusicUtil.songByGenre(genre.id)
GlideApp.with(activity)
.asBitmapPalette()
.load(RetroGlideExtension.getSongModel(genreSong))
.songCoverOptions(genreSong)
.into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(holder, colors)
}
})
// Just for a bit of shadow around image
holder.image?.outlineProvider = ViewOutlineProvider.BOUNDS
}
private fun setColors(holder: ViewHolder, color: MediaNotificationProcessor) {
holder.imageContainerCard?.setCardBackgroundColor(color.backgroundColor)
holder.title?.setTextColor(color.primaryTextColor)
holder.text?.setTextColor(color.secondaryTextColor)
} }
override fun getItemCount(): Int { override fun getItemCount(): Int {
@ -62,7 +98,6 @@ class GenreAdapter(
inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) { inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
override fun onClick(v: View?) { override fun onClick(v: View?) {
ViewCompat.setTransitionName(itemView, "genre")
listener.onClickGenre(dataSet[layoutPosition], itemView) listener.onClickGenre(dataSet[layoutPosition], itemView)
} }
} }

View file

@ -22,6 +22,7 @@ import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatTextView import androidx.appcompat.widget.AppCompatTextView
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.fragment.app.findFragment
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
@ -34,14 +35,15 @@ import code.name.monkey.retromusic.adapter.album.AlbumAdapter
import code.name.monkey.retromusic.adapter.artist.ArtistAdapter import code.name.monkey.retromusic.adapter.artist.ArtistAdapter
import code.name.monkey.retromusic.adapter.song.SongAdapter import code.name.monkey.retromusic.adapter.song.SongAdapter
import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.glide.SongGlideRequest import code.name.monkey.retromusic.fragments.home.HomeFragment
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.IAlbumClickListener import code.name.monkey.retromusic.interfaces.IAlbumClickListener
import code.name.monkey.retromusic.interfaces.IArtistClickListener import code.name.monkey.retromusic.interfaces.IArtistClickListener
import code.name.monkey.retromusic.interfaces.IGenreClickListener import code.name.monkey.retromusic.interfaces.IGenreClickListener
import code.name.monkey.retromusic.model.* import code.name.monkey.retromusic.model.*
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import com.bumptech.glide.Glide
import com.google.android.material.card.MaterialCardView import com.google.android.material.card.MaterialCardView
class HomeAdapter( class HomeAdapter(
@ -82,6 +84,7 @@ class HomeAdapter(
val viewHolder = holder as AlbumViewHolder val viewHolder = holder as AlbumViewHolder
viewHolder.bindView(home) viewHolder.bindView(home)
viewHolder.clickableArea.setOnClickListener { viewHolder.clickableArea.setOnClickListener {
it.findFragment<HomeFragment>().setSharedAxisXTransitions()
activity.findNavController(R.id.fragment_container).navigate( activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment, R.id.detailListFragment,
bundleOf("type" to RECENT_ALBUMS) bundleOf("type" to RECENT_ALBUMS)
@ -92,6 +95,7 @@ class HomeAdapter(
val viewHolder = holder as AlbumViewHolder val viewHolder = holder as AlbumViewHolder
viewHolder.bindView(home) viewHolder.bindView(home)
viewHolder.clickableArea.setOnClickListener { viewHolder.clickableArea.setOnClickListener {
it.findFragment<HomeFragment>().setSharedAxisXTransitions()
activity.findNavController(R.id.fragment_container).navigate( activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment, R.id.detailListFragment,
bundleOf("type" to TOP_ALBUMS) bundleOf("type" to TOP_ALBUMS)
@ -102,6 +106,7 @@ class HomeAdapter(
val viewHolder = holder as ArtistViewHolder val viewHolder = holder as ArtistViewHolder
viewHolder.bindView(home) viewHolder.bindView(home)
viewHolder.clickableArea.setOnClickListener { viewHolder.clickableArea.setOnClickListener {
it.findFragment<HomeFragment>().setSharedAxisXTransitions()
activity.findNavController(R.id.fragment_container).navigate( activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment, R.id.detailListFragment,
bundleOf("type" to RECENT_ARTISTS) bundleOf("type" to RECENT_ARTISTS)
@ -112,6 +117,7 @@ class HomeAdapter(
val viewHolder = holder as ArtistViewHolder val viewHolder = holder as ArtistViewHolder
viewHolder.bindView(home) viewHolder.bindView(home)
viewHolder.clickableArea.setOnClickListener { viewHolder.clickableArea.setOnClickListener {
it.findFragment<HomeFragment>().setSharedAxisXTransitions()
activity.findNavController(R.id.fragment_container).navigate( activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment, R.id.detailListFragment,
bundleOf("type" to TOP_ARTISTS) bundleOf("type" to TOP_ARTISTS)
@ -126,6 +132,7 @@ class HomeAdapter(
val viewHolder = holder as PlaylistViewHolder val viewHolder = holder as PlaylistViewHolder
viewHolder.bindView(home) viewHolder.bindView(home)
viewHolder.clickableArea.setOnClickListener { viewHolder.clickableArea.setOnClickListener {
it.findFragment<HomeFragment>().setSharedAxisXTransitions()
activity.findNavController(R.id.fragment_container).navigate( activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment, R.id.detailListFragment,
bundleOf("type" to FAVOURITES) bundleOf("type" to FAVOURITES)
@ -184,17 +191,29 @@ class HomeAdapter(
fun bindView(home: Home) { fun bindView(home: Home) {
val color = ThemeStore.accentColor(activity) val color = ThemeStore.accentColor(activity)
itemView.findViewById<TextView>(R.id.message).setTextColor(color) itemView.findViewById<TextView>(R.id.message).apply {
setTextColor(color)
setOnClickListener {
MusicPlayerRemote.playNext((home.arrayList as List<Song>).subList(0, 8))
if (!MusicPlayerRemote.isPlaying) {
MusicPlayerRemote.playNextSong()
}
}
}
itemView.findViewById<MaterialCardView>(R.id.card6).apply { itemView.findViewById<MaterialCardView>(R.id.card6).apply {
setCardBackgroundColor(ColorUtil.withAlpha(color, 0.12f)) setCardBackgroundColor(ColorUtil.withAlpha(color, 0.12f))
} }
images.forEachIndexed { index, id -> images.forEachIndexed { index, id ->
itemView.findViewById<View>(id).setOnClickListener { itemView.findViewById<View>(id).setOnClickListener {
MusicPlayerRemote.playNext(home.arrayList[index] as Song) MusicPlayerRemote.playNext(home.arrayList[index] as Song)
if (!MusicPlayerRemote.isPlaying) {
MusicPlayerRemote.playNextSong()
} }
SongGlideRequest.Builder.from(Glide.with(activity), home.arrayList[index] as Song) }
GlideApp.with(activity)
.asBitmap() .asBitmap()
.build() .songCoverOptions(home.arrayList[index] as Song)
.load(RetroGlideExtension.getSongModel(home.arrayList[index] as Song))
.into(itemView.findViewById(id)) .into(itemView.findViewById(id))
} }
} }
@ -207,7 +226,7 @@ class HomeAdapter(
val songAdapter = SongAdapter( val songAdapter = SongAdapter(
activity, activity,
home.arrayList as MutableList<Song>, home.arrayList as MutableList<Song>,
R.layout.item_album_card, null R.layout.item_favourite_card, null
) )
layoutManager = linearLayoutManager() layoutManager = linearLayoutManager()
adapter = songAdapter adapter = songAdapter
@ -257,7 +276,7 @@ class HomeAdapter(
bundleOf(EXTRA_ARTIST_ID to artistId), bundleOf(EXTRA_ARTIST_ID to artistId),
null, null,
FragmentNavigatorExtras( FragmentNavigatorExtras(
view to "artist" view to artistId.toString()
) )
) )
} }
@ -268,7 +287,7 @@ class HomeAdapter(
bundleOf(EXTRA_ALBUM_ID to albumId), bundleOf(EXTRA_ALBUM_ID to albumId),
null, null,
FragmentNavigatorExtras( FragmentNavigatorExtras(
view to "album" view to albumId.toString()
) )
) )
} }

View file

@ -29,15 +29,14 @@ import code.name.monkey.retromusic.*
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.db.PlaylistEntity import code.name.monkey.retromusic.db.PlaylistEntity
import code.name.monkey.retromusic.db.PlaylistWithSongs import code.name.monkey.retromusic.db.PlaylistWithSongs
import code.name.monkey.retromusic.glide.AlbumGlideRequest import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.ArtistGlideRequest import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.menu.SongMenuHelper import code.name.monkey.retromusic.helper.menu.SongMenuHelper
import code.name.monkey.retromusic.model.* import code.name.monkey.retromusic.model.*
import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist
import code.name.monkey.retromusic.repository.PlaylistSongsLoader import code.name.monkey.retromusic.repository.PlaylistSongsLoader
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import com.bumptech.glide.Glide
import java.util.* import java.util.*
class SearchAdapter( class SearchAdapter(
@ -52,7 +51,7 @@ class SearchAdapter(
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
if (dataSet[position] is Album) return ALBUM if (dataSet[position] is Album) return ALBUM
if (dataSet[position] is Artist) return ARTIST if (dataSet[position] is Artist) return if ((dataSet[position] as Artist).isAlbumArtist) ALBUM_ARTIST else ARTIST
if (dataSet[position] is Genre) return GENRE if (dataSet[position] is Genre) return GENRE
if (dataSet[position] is PlaylistEntity) return PLAYLIST if (dataSet[position] is PlaylistEntity) return PLAYLIST
return if (dataSet[position] is Song) SONG else HEADER return if (dataSet[position] is Song) SONG else HEADER
@ -66,6 +65,14 @@ class SearchAdapter(
false false
), viewType ), viewType
) )
else if (viewType == ALBUM || viewType == ARTIST || viewType== ALBUM_ARTIST)
ViewHolder(
LayoutInflater.from(activity).inflate(
R.layout.item_list_big,
parent,
false
), viewType
)
else else
ViewHolder( ViewHolder(
LayoutInflater.from(activity).inflate(R.layout.item_list, parent, false), LayoutInflater.from(activity).inflate(R.layout.item_list, parent, false),
@ -80,21 +87,23 @@ class SearchAdapter(
val album = dataSet[position] as Album val album = dataSet[position] as Album
holder.title?.text = album.title holder.title?.text = album.title
holder.text?.text = album.artistName holder.text?.text = album.artistName
AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong()) GlideApp.with(activity).asDrawable().albumCoverOptions(album.safeGetFirstSong()).load(RetroGlideExtension.getSongModel(album.safeGetFirstSong()))
.checkIgnoreMediaStore().build().into(holder.image) .into(holder.image!!)
} }
ARTIST -> { ARTIST -> {
holder.imageTextContainer?.isVisible = true holder.imageTextContainer?.isVisible = true
val artist = dataSet[position] as Artist val artist = dataSet[position] as Artist
holder.title?.text = artist.name holder.title?.text = artist.name
holder.text?.text = MusicUtil.getArtistInfoString(activity, artist) holder.text?.text = MusicUtil.getArtistInfoString(activity, artist)
ArtistGlideRequest.Builder.from(Glide.with(activity), artist).build() GlideApp.with(activity).asDrawable().artistImageOptions(artist).load(
.into(holder.image) RetroGlideExtension.getArtistModel(artist)).into(holder.image!!)
} }
SONG -> { SONG -> {
holder.imageTextContainer?.isVisible = true
val song = dataSet[position] as Song val song = dataSet[position] as Song
holder.title?.text = song.title holder.title?.text = song.title
holder.text?.text = song.albumName holder.text?.text = song.albumName
GlideApp.with(activity).asDrawable().songCoverOptions(song).load(RetroGlideExtension.getSongModel(song)).into(holder.image!!)
} }
GENRE -> { GENRE -> {
val genre = dataSet[position] as Genre val genre = dataSet[position] as Genre
@ -113,6 +122,14 @@ class SearchAdapter(
holder.title?.text = playlist.playlistName holder.title?.text = playlist.playlistName
//holder.text?.text = MusicUtil.playlistInfoString(activity, playlist.songs) //holder.text?.text = MusicUtil.playlistInfoString(activity, playlist.songs)
} }
ALBUM_ARTIST -> {
holder.imageTextContainer?.isVisible = true
val artist = dataSet[position] as Artist
holder.title?.text = artist.name
holder.text?.text = MusicUtil.getArtistInfoString(activity, artist)
GlideApp.with(activity).asDrawable().artistImageOptions(artist).load(artist)
.into(holder.image!!)
}
else -> { else -> {
holder.title?.text = dataSet[position].toString() holder.title?.text = dataSet[position].toString()
holder.title?.setTextColor(ThemeStore.accentColor(activity)) holder.title?.setTextColor(ThemeStore.accentColor(activity))
@ -174,6 +191,12 @@ class SearchAdapter(
bundleOf(EXTRA_ARTIST_ID to (item as Artist).id) 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 -> { GENRE -> {
activity.findNavController(R.id.fragment_container).navigate( activity.findNavController(R.id.fragment_container).navigate(
R.id.genreDetailsFragment, R.id.genreDetailsFragment,
@ -202,5 +225,6 @@ class SearchAdapter(
private const val SONG = 3 private const val SONG = 3
private const val GENRE = 4 private const val GENRE = 4
private const val PLAYLIST = 5 private const val PLAYLIST = 5
private const val ALBUM_ARTIST = 6
} }
} }

View file

@ -24,19 +24,20 @@ import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.audiocover.AudioFileCover import code.name.monkey.retromusic.glide.audiocover.AudioFileCover
import code.name.monkey.retromusic.interfaces.ICabHolder import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.interfaces.ICallbacks import code.name.monkey.retromusic.interfaces.ICallbacks
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.signature.MediaStoreSignature import com.bumptech.glide.signature.MediaStoreSignature
import me.zhanghai.android.fastscroll.PopupTextProvider
import java.io.File import java.io.File
import java.text.DecimalFormat import java.text.DecimalFormat
import kotlin.math.log10 import kotlin.math.log10
import kotlin.math.pow import kotlin.math.pow
import me.zhanghai.android.fastscroll.PopupTextProvider
class SongFileAdapter( class SongFileAdapter(
private val activity: AppCompatActivity, private val activity: AppCompatActivity,
@ -111,14 +112,14 @@ class SongFileAdapter(
val error = RetroUtil.getTintedVectorDrawable( val error = RetroUtil.getTintedVectorDrawable(
activity, R.drawable.ic_file_music, iconColor activity, R.drawable.ic_file_music, iconColor
) )
Glide.with(activity) GlideApp.with(activity)
.load(AudioFileCover(file.path)) .load(AudioFileCover(file.path))
.diskCacheStrategy(DiskCacheStrategy.NONE) .diskCacheStrategy(DiskCacheStrategy.NONE)
.error(error) .error(error)
.placeholder(error) .placeholder(error)
.animate(android.R.anim.fade_in) .transition(RetroGlideExtension.getDefaultTransition())
.signature(MediaStoreSignature("", file.lastModified(), 0)) .signature(MediaStoreSignature("", file.lastModified(), 0))
.into(holder.image) .into(holder.image!!)
} }
} }
@ -126,7 +127,7 @@ class SongFileAdapter(
return dataSet.size return dataSet.size
} }
override fun getIdentifier(position: Int): File? { override fun getIdentifier(position: Int): File {
return dataSet[position] return dataSet[position]
} }

View file

@ -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<Storage>,
val storageClickListener: StorageClickListener
) :
RecyclerView.Adapter<StorageAdapter.ViewHolder>() {
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
}

View file

@ -24,7 +24,8 @@ import androidx.fragment.app.FragmentActivity
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.glide.AlbumGlideRequest import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.SortOrder import code.name.monkey.retromusic.helper.SortOrder
import code.name.monkey.retromusic.helper.menu.SongsMenuHelper import code.name.monkey.retromusic.helper.menu.SongsMenuHelper
@ -35,7 +36,6 @@ import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
import me.zhanghai.android.fastscroll.PopupTextProvider import me.zhanghai.android.fastscroll.PopupTextProvider
open class AlbumAdapter( open class AlbumAdapter(
@ -73,7 +73,13 @@ open class AlbumAdapter(
} }
protected open fun getAlbumText(album: Album): String? { 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) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
@ -82,6 +88,7 @@ open class AlbumAdapter(
holder.itemView.isActivated = isChecked holder.itemView.isActivated = isChecked
holder.title?.text = getAlbumTitle(album) holder.title?.text = getAlbumTitle(album)
holder.text?.text = getAlbumText(album) holder.text?.text = getAlbumText(album)
ViewCompat.setTransitionName(holder.image!!, album.id.toString())
loadAlbumCover(album, holder) loadAlbumCover(album, holder)
} }
@ -92,17 +99,17 @@ open class AlbumAdapter(
holder.paletteColorContainer?.setBackgroundColor(color.backgroundColor) holder.paletteColorContainer?.setBackgroundColor(color.backgroundColor)
} }
holder.mask?.backgroundTintList = ColorStateList.valueOf(color.primaryTextColor) holder.mask?.backgroundTintList = ColorStateList.valueOf(color.primaryTextColor)
holder.imageContainerCard?.setCardBackgroundColor(color.backgroundColor) } holder.imageContainerCard?.setCardBackgroundColor(color.backgroundColor)
}
protected open fun loadAlbumCover(album: Album, holder: ViewHolder) { protected open fun loadAlbumCover(album: Album, holder: ViewHolder) {
if (holder.image == null) { if (holder.image == null) {
return return
} }
val song = album.safeGetFirstSong()
AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong()) GlideApp.with(activity).asBitmapPalette().albumCoverOptions(song)
.checkIgnoreMediaStore() //.checkIgnoreMediaStore()
.generatePalette(activity) .load(RetroGlideExtension.getSongModel(song))
.build()
.into(object : RetroMusicColoredTarget(holder.image!!) { .into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onColorReady(colors: MediaNotificationProcessor) { override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(colors, holder) setColors(colors, holder)
@ -161,7 +168,6 @@ open class AlbumAdapter(
inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) { inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
init { init {
setImageTransitionName("Album")
menu?.visibility = View.GONE menu?.visibility = View.GONE
} }
@ -171,16 +177,13 @@ open class AlbumAdapter(
toggleChecked(layoutPosition) toggleChecked(layoutPosition)
} else { } else {
image?.let { image?.let {
ViewCompat.setTransitionName(it, "album")
listener?.onAlbumClick(dataSet[layoutPosition].id, it) listener?.onAlbumClick(dataSet[layoutPosition].id, it)
} }
} }
} }
override fun onLongClick(v: View?): Boolean { override fun onLongClick(v: View?): Boolean {
toggleChecked(layoutPosition) return toggleChecked(layoutPosition)
return super.onLongClick(v)
} }
} }

View file

@ -26,15 +26,15 @@ import androidx.lifecycle.lifecycleScope
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.fragments.AlbumCoverStyle import code.name.monkey.retromusic.fragments.AlbumCoverStyle
import code.name.monkey.retromusic.fragments.NowPlayingScreen.* import code.name.monkey.retromusic.fragments.NowPlayingScreen.*
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.misc.CustomFragmentStatePagerAdapter import code.name.monkey.retromusic.misc.CustomFragmentStatePagerAdapter
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.NavigationUtil import code.name.monkey.retromusic.util.NavigationUtil
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -162,9 +162,10 @@ class AlbumCoverPagerAdapter(
} }
private fun loadAlbumCover() { private fun loadAlbumCover() {
SongGlideRequest.Builder.from(Glide.with(requireContext()), song) GlideApp.with(this).asBitmapPalette().songCoverOptions(song)
.checkIgnoreMediaStore(requireContext()) //.checkIgnoreMediaStore()
.generatePalette(requireContext()).build() .load(RetroGlideExtension.getSongModel(song))
.dontAnimate()
.into(object : RetroMusicColoredTarget(albumCover) { .into(object : RetroMusicColoredTarget(albumCover) {
override fun onColorReady(colors: MediaNotificationProcessor) { override fun onColorReady(colors: MediaNotificationProcessor) {
setColor(colors) setColor(colors)

View file

@ -17,7 +17,8 @@ package code.name.monkey.retromusic.adapter.album
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import code.name.monkey.retromusic.glide.AlbumGlideRequest import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.HorizontalAdapterHelper import code.name.monkey.retromusic.helper.HorizontalAdapterHelper
import code.name.monkey.retromusic.interfaces.IAlbumClickListener import code.name.monkey.retromusic.interfaces.IAlbumClickListener
@ -25,7 +26,6 @@ import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
class HorizontalAlbumAdapter( class HorizontalAlbumAdapter(
activity: FragmentActivity, activity: FragmentActivity,
@ -49,10 +49,8 @@ class HorizontalAlbumAdapter(
override fun loadAlbumCover(album: Album, holder: ViewHolder) { override fun loadAlbumCover(album: Album, holder: ViewHolder) {
if (holder.image == null) return if (holder.image == null) return
AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong()) GlideApp.with(activity).asBitmapPalette().albumCoverOptions(album.safeGetFirstSong())
.checkIgnoreMediaStore() .load(RetroGlideExtension.getSongModel(album.safeGetFirstSong()))
.generatePalette(activity)
.build()
.into(object : RetroMusicColoredTarget(holder.image!!) { .into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onColorReady(colors: MediaNotificationProcessor) { override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(colors, holder) setColors(colors, holder)
@ -60,7 +58,7 @@ class HorizontalAlbumAdapter(
}) })
} }
override fun getAlbumText(album: Album): String? { override fun getAlbumText(album: Album): String {
return MusicUtil.getYearString(album.year) return MusicUtil.getYearString(album.year)
} }

View file

@ -26,25 +26,28 @@ import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.glide.ArtistGlideRequest import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.menu.SongsMenuHelper import code.name.monkey.retromusic.helper.menu.SongsMenuHelper
import code.name.monkey.retromusic.interfaces.IAlbumArtistClickListener
import code.name.monkey.retromusic.interfaces.IArtistClickListener import code.name.monkey.retromusic.interfaces.IArtistClickListener
import code.name.monkey.retromusic.interfaces.ICabHolder import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
import java.util.*
import me.zhanghai.android.fastscroll.PopupTextProvider import me.zhanghai.android.fastscroll.PopupTextProvider
import java.util.*
class ArtistAdapter( class ArtistAdapter(
val activity: FragmentActivity, val activity: FragmentActivity,
var dataSet: List<Artist>, var dataSet: List<Artist>,
var itemLayoutRes: Int, var itemLayoutRes: Int,
val ICabHolder: ICabHolder?, val ICabHolder: ICabHolder?,
val IArtistClickListener: IArtistClickListener val IArtistClickListener: IArtistClickListener,
val IAlbumArtistClickListener: IAlbumArtistClickListener? = null
) : AbsMultiSelectAdapter<ArtistAdapter.ViewHolder, Artist>( ) : AbsMultiSelectAdapter<ArtistAdapter.ViewHolder, Artist>(
activity, ICabHolder, R.menu.menu_media_selection activity, ICabHolder, R.menu.menu_media_selection
), PopupTextProvider { ), PopupTextProvider {
@ -82,6 +85,13 @@ class ArtistAdapter(
holder.itemView.isActivated = isChecked holder.itemView.isActivated = isChecked
holder.title?.text = artist.name holder.title?.text = artist.name
holder.text?.hide() holder.text?.hide()
holder.image?.let {
if (PreferenceUtil.albumArtistsOnly) {
ViewCompat.setTransitionName(it, artist.name)
} else {
ViewCompat.setTransitionName(it, artist.id.toString())
}
}
loadArtistImage(artist, holder) loadArtistImage(artist, holder)
} }
@ -98,9 +108,11 @@ class ArtistAdapter(
if (holder.image == null) { if (holder.image == null) {
return return
} }
ArtistGlideRequest.Builder.from(Glide.with(activity), artist) GlideApp.with(activity)
.generatePalette(activity) .asBitmapPalette()
.build() .load(RetroGlideExtension.getArtistModel(artist))
.artistImageOptions(artist)
.transition(RetroGlideExtension.getDefaultTransition())
.into(object : RetroMusicColoredTarget(holder.image!!) { .into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onColorReady(colors: MediaNotificationProcessor) { override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(colors, holder) setColors(colors, holder)
@ -112,7 +124,7 @@ class ArtistAdapter(
return dataSet.size return dataSet.size
} }
override fun getIdentifier(position: Int): Artist? { override fun getIdentifier(position: Int): Artist {
return dataSet[position] return dataSet[position]
} }
@ -154,16 +166,19 @@ class ArtistAdapter(
if (isInQuickSelectMode) { if (isInQuickSelectMode) {
toggleChecked(layoutPosition) toggleChecked(layoutPosition)
} else { } else {
val artist = dataSet[layoutPosition]
image?.let { image?.let {
ViewCompat.setTransitionName(it, "artist") if (PreferenceUtil.albumArtistsOnly && IAlbumArtistClickListener != null) {
IArtistClickListener.onArtist(dataSet[layoutPosition].id, it) IAlbumArtistClickListener.onAlbumArtist(artist.name, it)
} else {
IArtistClickListener.onArtist(artist.id, it)
}
} }
} }
} }
override fun onLongClick(v: View?): Boolean { override fun onLongClick(v: View?): Boolean {
toggleChecked(layoutPosition) return toggleChecked(layoutPosition)
return super.onLongClick(v)
} }
} }
} }

View file

@ -1,26 +1,36 @@
package code.name.monkey.retromusic.adapter.base; package code.name.monkey.retromusic.adapter.base;
import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import androidx.annotation.MenuRes; import androidx.annotation.MenuRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.interfaces.ICabHolder;
import com.afollestad.materialcab.MaterialCab; import com.afollestad.materialcab.MaterialCab;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.interfaces.ICabHolder;
public abstract class AbsMultiSelectAdapter<V extends RecyclerView.ViewHolder, I> public abstract class AbsMultiSelectAdapter<V extends RecyclerView.ViewHolder, I>
extends RecyclerView.Adapter<V> implements MaterialCab.Callback { extends RecyclerView.Adapter<V> implements MaterialCab.Callback {
@Nullable private final ICabHolder ICabHolder; @Nullable
private final ICabHolder ICabHolder;
private final Context context; private final Context context;
private MaterialCab cab; private MaterialCab cab;
private List<I> checked; private final List<I> checked;
private int menuRes; private int menuRes;
private AppCompatTextView dummyText;
private int oldSize = 0;
public AbsMultiSelectAdapter( public AbsMultiSelectAdapter(
@NonNull Context context, @Nullable ICabHolder ICabHolder, @MenuRes int menuRes) { @NonNull Context context, @Nullable ICabHolder ICabHolder, @MenuRes int menuRes) {
@ -32,12 +42,16 @@ public abstract class AbsMultiSelectAdapter<V extends RecyclerView.ViewHolder, I
@Override @Override
public boolean onCabCreated(MaterialCab materialCab, Menu menu) { public boolean onCabCreated(MaterialCab materialCab, Menu menu) {
playCreateAnim(materialCab);
createDummyTextView();
return true; return true;
} }
@Override @Override
public boolean onCabFinished(MaterialCab materialCab) { public boolean onCabFinished(MaterialCab materialCab) {
clearChecked(); clearChecked();
cab.getToolbar().removeView(dummyText);
oldSize = 0;
return true; return true;
} }
@ -111,6 +125,7 @@ public abstract class AbsMultiSelectAdapter<V extends RecyclerView.ViewHolder, I
notifyDataSetChanged(); notifyDataSetChanged();
} }
@SuppressLint({"StringFormatInvalid", "StringFormatMatches"})
private void updateCab() { private void updateCab() {
if (ICabHolder != null) { if (ICabHolder != null) {
if (cab == null || !cab.isActive()) { if (cab == null || !cab.isActive()) {
@ -120,10 +135,48 @@ public abstract class AbsMultiSelectAdapter<V extends RecyclerView.ViewHolder, I
if (size <= 0) { if (size <= 0) {
cab.finish(); cab.finish();
} else if (size == 1) { } else if (size == 1) {
cab.setTitle(getName(checked.get(0)));
} else {
cab.setTitle(context.getString(R.string.x_selected, size)); cab.setTitle(context.getString(R.string.x_selected, size));
if (oldSize == 0) {
cab.getToolbar().addView(dummyText);
}
} else {
AppCompatTextView title = (AppCompatTextView) cab.getToolbar().getChildAt(2);
dummyText.setText(title.getText());
title.setAlpha(0);
cab.setTitle(context.getString(R.string.x_selected, size));
dummyText.setTranslationX(title.getLeft() - dummyText.getLeft());
dummyText.setAlpha(1);
dummyText.setTranslationY(0);
if (oldSize > size) {
title.setTranslationY(40);
dummyText.animate().translationY(-40).alpha(0.0F).setDuration(300).start();
} else {
title.setTranslationY(-40);
dummyText.animate().translationY(40).alpha(0.0F).setDuration(300).start();
}
title.animate().translationY(0).alpha(1.0F).setDuration(300).start();
}
oldSize = size;
} }
} }
private void playCreateAnim(MaterialCab materialCab) {
Toolbar cabToolbar = materialCab.getToolbar();
int height = context.getResources().getDimensionPixelSize(R.dimen.toolbar_height);
cabToolbar.setTranslationY(-height);
cabToolbar.animate().translationYBy(height).setDuration(300).start();
}
private void createDummyTextView() {
if (dummyText != null) return;
dummyText = new AppCompatTextView(context);
dummyText.setSingleLine();
dummyText.setTextAppearance(context, R.style.ToolbarTextAppearanceNormal);
Toolbar.LayoutParams l1 = new Toolbar.LayoutParams(Toolbar.LayoutParams.WRAP_CONTENT, Toolbar.LayoutParams.WRAP_CONTENT);
dummyText.setLayoutParams(l1);
} }
} }

View file

@ -24,11 +24,11 @@ import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView; import androidx.appcompat.widget.AppCompatImageView;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import code.name.monkey.retromusic.R;
import com.google.android.material.card.MaterialCardView; import com.google.android.material.card.MaterialCardView;
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractDraggableSwipeableItemViewHolder; import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractDraggableSwipeableItemViewHolder;
import code.name.monkey.retromusic.R;
public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHolder public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHolder
implements View.OnLongClickListener, View.OnClickListener { implements View.OnLongClickListener, View.OnClickListener {

View file

@ -15,7 +15,6 @@
package code.name.monkey.retromusic.adapter.playlist package code.name.monkey.retromusic.adapter.playlist
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.Drawable
import android.text.TextUtils import android.text.TextUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
@ -24,29 +23,22 @@ import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.db.PlaylistEntity import code.name.monkey.retromusic.db.PlaylistEntity
import code.name.monkey.retromusic.db.PlaylistWithSongs import code.name.monkey.retromusic.db.PlaylistWithSongs
import code.name.monkey.retromusic.db.SongEntity
import code.name.monkey.retromusic.db.toSongs import code.name.monkey.retromusic.db.toSongs
import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.playlistPreview.PlaylistPreview
import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper
import code.name.monkey.retromusic.helper.menu.SongsMenuHelper import code.name.monkey.retromusic.helper.menu.SongsMenuHelper
import code.name.monkey.retromusic.interfaces.ICabHolder import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.interfaces.IPlaylistClickListener import code.name.monkey.retromusic.interfaces.IPlaylistClickListener
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.AutoGeneratedPlaylistBitmap
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class PlaylistAdapter( class PlaylistAdapter(
private val activity: FragmentActivity, private val activity: FragmentActivity,
@ -95,27 +87,23 @@ class PlaylistAdapter(
holder.itemView.isActivated = isChecked(playlist) holder.itemView.isActivated = isChecked(playlist)
holder.title?.text = getPlaylistTitle(playlist.playlistEntity) holder.title?.text = getPlaylistTitle(playlist.playlistEntity)
holder.text?.text = getPlaylistText(playlist) holder.text?.text = getPlaylistText(playlist)
holder.image?.setImageDrawable(getIconRes())
val isChecked = isChecked(playlist) val isChecked = isChecked(playlist)
if (isChecked) { if (isChecked) {
holder.menu?.hide() holder.menu?.hide()
} else { } else {
holder.menu?.show() holder.menu?.show()
} }
//playlistBitmapLoader(activity, holder, playlist) GlideApp.with(activity)
.load(PlaylistPreview(playlist))
.playlistOptions()
.into(holder.image!!)
} }
private fun getIconRes(): Drawable = TintHelper.createTintedDrawable(
activity,
R.drawable.ic_playlist_play,
ATHUtil.resolveColor(activity, R.attr.colorControlNormal)
)
override fun getItemCount(): Int { override fun getItemCount(): Int {
return dataSet.size return dataSet.size
} }
override fun getIdentifier(position: Int): PlaylistWithSongs? { override fun getIdentifier(position: Int): PlaylistWithSongs {
return dataSet[position] return dataSet[position]
} }
@ -141,18 +129,8 @@ class PlaylistAdapter(
return songs return songs
} }
private fun getSongs(playlist: PlaylistWithSongs): List<SongEntity> =
mutableListOf<SongEntity>().apply {
addAll(playlist.songs)
}
inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) { inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
init { init {
image?.apply {
val iconPadding =
activity.resources.getDimensionPixelSize(R.dimen.list_item_image_icon_padding)
setPadding(iconPadding, iconPadding, iconPadding, iconPadding)
}
menu?.setOnClickListener { view -> menu?.setOnClickListener { view ->
val popupMenu = PopupMenu(activity, view) val popupMenu = PopupMenu(activity, view)
popupMenu.inflate(R.menu.menu_item_playlist) popupMenu.inflate(R.menu.menu_item_playlist)
@ -183,37 +161,6 @@ class PlaylistAdapter(
} }
} }
private fun playlistBitmapLoader(
activity: FragmentActivity,
viewHolder: ViewHolder,
playlist: PlaylistWithSongs
) {
activity.lifecycleScope.launch(IO) {
val songs = playlist.songs.toSongs()
val bitmap = AutoGeneratedPlaylistBitmap.getBitmap(activity, songs, false, false)
withContext(Main) { viewHolder.image?.setImageBitmap(bitmap) }
}
/*
override fun doInBackground(vararg params: Void?): Bitmap {
val songs = playlist.songs.toSongs()
return AutoGeneratedPlaylistBitmap.getBitmap(activity, songs, false, false)
}
override fun onPostExecute(result: Bitmap?) {
super.onPostExecute(result)
viewHolder.image?.setImageBitmap(result)
val color = RetroColorUtil.getColor(
RetroColorUtil.generatePalette(
result
),
ATHUtil.resolveColor(activity, R.attr.colorSurface)
)
viewHolder.paletteColorContainer?.setBackgroundColor(color)
}*/
}
companion object { companion object {
val TAG: String = PlaylistAdapter::class.java.simpleName val TAG: String = PlaylistAdapter::class.java.simpleName
} }

View file

@ -17,105 +17,99 @@ package code.name.monkey.retromusic.adapter.song
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.R.menu
import code.name.monkey.retromusic.db.PlaylistEntity import code.name.monkey.retromusic.db.PlaylistEntity
import code.name.monkey.retromusic.db.toSongEntity import code.name.monkey.retromusic.db.toSongEntity
import code.name.monkey.retromusic.db.toSongs import code.name.monkey.retromusic.db.toSongsEntity
import code.name.monkey.retromusic.dialogs.RemoveSongFromPlaylistDialog import code.name.monkey.retromusic.dialogs.RemoveSongFromPlaylistDialog
import code.name.monkey.retromusic.extensions.applyColor
import code.name.monkey.retromusic.extensions.applyOutlineColor
import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.ICabHolder import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.model.PlaylistSong
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.ViewUtil import com.google.android.material.button.MaterialButton
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemViewHolder
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange
import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.koin.androidx.viewmodel.ext.android.viewModel
class OrderablePlaylistSongAdapter( class OrderablePlaylistSongAdapter(
private val playlist: PlaylistEntity, private val playlist: PlaylistEntity,
activity: FragmentActivity, activity: FragmentActivity,
dataSet: ArrayList<Song>, dataSet: MutableList<Song>,
itemLayoutRes: Int, itemLayoutRes: Int,
ICabHolder: ICabHolder?, ICabHolder: ICabHolder?,
private val onMoveItemListener: OnMoveItemListener? ) : AbsOffsetSongAdapter(activity, dataSet, itemLayoutRes, ICabHolder),
) : SongAdapter( DraggableItemAdapter<OrderablePlaylistSongAdapter.ViewHolder> {
activity,
dataSet, val libraryViewModel: LibraryViewModel by activity.viewModel()
itemLayoutRes, val tempDataSet = dataSet
ICabHolder
), DraggableItemAdapter<OrderablePlaylistSongAdapter.ViewHolder> {
init { init {
setMultiSelectMenuRes(menu.menu_playlists_songs_selection) this.setHasStableIds(true)
this.setMultiSelectMenuRes(R.menu.menu_playlists_songs_selection)
}
override fun getItemId(position: Int): Long {
// requires static value, it means need to keep the same value
// even if the item position has been changed.
return if (position != 0) {
dataSet[position - 1].id
} else {
-1
}
} }
override fun createViewHolder(view: View): SongAdapter.ViewHolder { override fun createViewHolder(view: View): SongAdapter.ViewHolder {
return ViewHolder(view) return ViewHolder(view)
} }
override fun getItemId(position: Int): Long { override fun getItemViewType(position: Int): Int {
var positionFinal = position return if (position == 0) OFFSET_ITEM else SONG
positionFinal-- }
var long: Long = 0 override fun onBindViewHolder(holder: SongAdapter.ViewHolder, position: Int) {
if (positionFinal < 0) { if (holder.itemViewType == OFFSET_ITEM) {
long = -2 val color = ThemeStore.accentColor(activity)
val viewHolder = holder as ViewHolder
viewHolder.playAction?.let {
it.setOnClickListener {
MusicPlayerRemote.openQueue(dataSet, 0, true)
}
it.applyOutlineColor(color)
}
viewHolder.shuffleAction?.let {
it.setOnClickListener {
MusicPlayerRemote.openAndShuffleQueue(dataSet, true)
}
it.applyColor(color)
}
} else { } else {
if (dataSet[positionFinal] is PlaylistSong) { super.onBindViewHolder(holder, position - 1)
long = (dataSet[positionFinal] as PlaylistSong).idInPlayList.toLong()
} }
} }
return long
}
override fun onMultipleItemAction(menuItem: MenuItem, selection: List<Song>) { override fun onMultipleItemAction(menuItem: MenuItem, selection: List<Song>) {
when (menuItem.itemId) { when (menuItem.itemId) {
R.id.action_remove_from_playlist -> { R.id.action_remove_from_playlist -> RemoveSongFromPlaylistDialog.create(
RemoveSongFromPlaylistDialog.create(selection.toSongs(playlist.playListId)) selection.toSongsEntity(
playlist
)
)
.show(activity.supportFragmentManager, "REMOVE_FROM_PLAYLIST") .show(activity.supportFragmentManager, "REMOVE_FROM_PLAYLIST")
return else -> super.onMultipleItemAction(menuItem, selection)
}
}
super.onMultipleItemAction(menuItem, selection)
}
override fun onCheckCanStartDrag(holder: ViewHolder, position: Int, x: Int, y: Int): Boolean {
return onMoveItemListener != null && position > 0 && (ViewUtil.hitTest(
holder.dragView!!, x, y
) || ViewUtil.hitTest(holder.image!!, x, y))
}
override fun onGetItemDraggableRange(holder: ViewHolder, position: Int): ItemDraggableRange {
return ItemDraggableRange(1, dataSet.size)
}
override fun onMoveItem(fromPosition: Int, toPosition: Int) {
if (onMoveItemListener != null && fromPosition != toPosition) {
onMoveItemListener.onMoveItem(fromPosition - 1, toPosition - 1)
} }
} }
override fun onCheckCanDrop(draggingPosition: Int, dropPosition: Int): Boolean { inner class ViewHolder(itemView: View) : AbsOffsetSongAdapter.ViewHolder(itemView) {
return dropPosition > 0 val playAction: MaterialButton? = itemView.findViewById(R.id.playAction)
} val shuffleAction: MaterialButton? = itemView.findViewById(R.id.shuffleAction)
override fun onItemDragStarted(position: Int) {
notifyDataSetChanged()
}
override fun onItemDragFinished(fromPosition: Int, toPosition: Int, result: Boolean) {
notifyDataSetChanged()
}
interface OnMoveItemListener {
fun onMoveItem(fromPosition: Int, toPosition: Int)
}
inner class ViewHolder(itemView: View) : SongAdapter.ViewHolder(itemView),
DraggableItemViewHolder {
@DraggableItemStateFlags
private var mDragStateFlags: Int = 0
override var songMenuRes: Int override var songMenuRes: Int
get() = R.menu.menu_item_playlist_song get() = R.menu.menu_item_playlist_song
@ -123,16 +117,6 @@ class OrderablePlaylistSongAdapter(
super.songMenuRes = value super.songMenuRes = value
} }
init {
if (dragView != null) {
if (onMoveItemListener != null) {
dragView?.visibility = View.VISIBLE
} else {
dragView?.visibility = View.GONE
}
}
}
override fun onSongMenuItemClick(item: MenuItem): Boolean { override fun onSongMenuItemClick(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.action_remove_from_playlist -> { R.id.action_remove_from_playlist -> {
@ -144,13 +128,58 @@ class OrderablePlaylistSongAdapter(
return super.onSongMenuItemClick(item) return super.onSongMenuItemClick(item)
} }
@DraggableItemStateFlags init {
override fun getDragStateFlags(): Int { dragView?.visibility = View.VISIBLE
return mDragStateFlags
} }
override fun setDragStateFlags(@DraggableItemStateFlags flags: Int) { override fun onClick(v: View?) {
mDragStateFlags = flags if (itemViewType == OFFSET_ITEM) {
MusicPlayerRemote.openAndShuffleQueue(dataSet, true)
return
}
super.onClick(v)
}
}
override fun onCheckCanStartDrag(holder: ViewHolder, position: Int, x: Int, y: Int): Boolean {
if (dataSet.size == 0 or 1) {
return false
}
val dragHandle = holder.dragView ?: return false
val handleWidth = dragHandle.width
val handleHeight = dragHandle.height
val handleLeft = dragHandle.left
val handleTop = dragHandle.top
return (x >= handleLeft && x < handleLeft + handleWidth &&
y >= handleTop && y < handleTop + handleHeight) && position != 0
}
override fun onMoveItem(fromPosition: Int, toPosition: Int) {
dataSet.add(toPosition - 1, dataSet.removeAt(fromPosition - 1))
}
override fun onGetItemDraggableRange(holder: ViewHolder, position: Int): ItemDraggableRange {
return ItemDraggableRange(1, itemCount - 1)
}
override fun onCheckCanDrop(draggingPosition: Int, dropPosition: Int): Boolean {
return true
}
override fun onItemDragStarted(position: Int) {
notifyDataSetChanged()
}
override fun onItemDragFinished(fromPosition: Int, toPosition: Int, result: Boolean) {
notifyDataSetChanged()
}
fun saveSongs(playlistEntity: PlaylistEntity) {
activity.lifecycleScope.launch(Dispatchers.IO) {
libraryViewModel.insertSongs(dataSet.toSongsEntity(playlistEntity))
} }
} }
} }

View file

@ -19,8 +19,9 @@ import android.view.View
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicPlayerRemote.isPlaying import code.name.monkey.retromusic.helper.MusicPlayerRemote.isPlaying
import code.name.monkey.retromusic.helper.MusicPlayerRemote.playNextSong import code.name.monkey.retromusic.helper.MusicPlayerRemote.playNextSong
@ -29,7 +30,6 @@ import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.ViewUtil import code.name.monkey.retromusic.util.ViewUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange
import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags
@ -79,9 +79,8 @@ class PlayingQueueAdapter(
if (holder.image == null) { if (holder.image == null) {
return return
} }
SongGlideRequest.Builder.from(Glide.with(activity), song) GlideApp.with(activity).asBitmapPalette().songCoverOptions(song)
.checkIgnoreMediaStore(activity) .load(RetroGlideExtension.getSongModel(song))
.generatePalette(activity).build()
.into(object : RetroMusicColoredTarget(holder.image!!) { .into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onColorReady(colors: MediaNotificationProcessor) { override fun onColorReady(colors: MediaNotificationProcessor) {
//setColors(colors, holder) //setColors(colors, holder)

View file

@ -23,6 +23,8 @@ import code.name.monkey.retromusic.extensions.applyOutlineColor
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.ICabHolder import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.model.Song 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 import com.google.android.material.button.MaterialButton
class ShuffleButtonSongAdapter( class ShuffleButtonSongAdapter(
@ -32,10 +34,15 @@ class ShuffleButtonSongAdapter(
ICabHolder: ICabHolder? ICabHolder: ICabHolder?
) : AbsOffsetSongAdapter(activity, dataSet, itemLayoutRes, ICabHolder) { ) : AbsOffsetSongAdapter(activity, dataSet, itemLayoutRes, ICabHolder) {
override fun createViewHolder(view: View): SongAdapter.ViewHolder { override fun createViewHolder(view: View): SongAdapter.ViewHolder {
return ViewHolder(view) 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) { override fun onBindViewHolder(holder: SongAdapter.ViewHolder, position: Int) {
if (holder.itemViewType == OFFSET_ITEM) { if (holder.itemViewType == OFFSET_ITEM) {
val color = ThemeStore.accentColor(activity) val color = ThemeStore.accentColor(activity)
@ -54,6 +61,10 @@ class ShuffleButtonSongAdapter(
} }
} else { } else {
super.onBindViewHolder(holder, position - 1) super.onBindViewHolder(holder, position - 1)
val landscape = RetroUtil.isLandscape()
if ((PreferenceUtil.songGridSize > 2 && !landscape) || (PreferenceUtil.songGridSizeLand > 5 && landscape)) {
holder.menu?.visibility = View.GONE
}
} }
} }
@ -69,4 +80,5 @@ class ShuffleButtonSongAdapter(
super.onClick(v) super.onClick(v)
} }
} }
} }

View file

@ -30,8 +30,9 @@ import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.SortOrder import code.name.monkey.retromusic.helper.SortOrder
import code.name.monkey.retromusic.helper.menu.SongMenuHelper import code.name.monkey.retromusic.helper.menu.SongMenuHelper
@ -42,7 +43,6 @@ import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.afollestad.materialcab.MaterialCab import com.afollestad.materialcab.MaterialCab
import com.bumptech.glide.Glide
import me.zhanghai.android.fastscroll.PopupTextProvider import me.zhanghai.android.fastscroll.PopupTextProvider
/** /**
@ -120,9 +120,8 @@ open class SongAdapter(
if (holder.image == null) { if (holder.image == null) {
return return
} }
SongGlideRequest.Builder.from(Glide.with(activity), song) GlideApp.with(activity).asBitmapPalette().songCoverOptions(song)
.checkIgnoreMediaStore(activity) .load(RetroGlideExtension.getSongModel(song))
.generatePalette(activity).build()
.into(object : RetroMusicColoredTarget(holder.image!!) { .into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onColorReady(colors: MediaNotificationProcessor) { override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(colors, holder) setColors(colors, holder)
@ -130,15 +129,15 @@ open class SongAdapter(
}) })
} }
private fun getSongTitle(song: Song): String? { private fun getSongTitle(song: Song): String {
return song.title return song.title
} }
private fun getSongText(song: Song): String? { private fun getSongText(song: Song): String {
return song.artistName return song.artistName
} }
private fun getSongText2(song: Song): String? { private fun getSongText2(song: Song): String {
return song.albumName return song.albumName
} }
@ -165,6 +164,7 @@ open class SongAdapter(
SortOrder.SongSortOrder.SONG_ARTIST -> dataSet[position].artistName SortOrder.SongSortOrder.SONG_ARTIST -> dataSet[position].artistName
SortOrder.SongSortOrder.SONG_YEAR -> return MusicUtil.getYearString(dataSet[position].year) SortOrder.SongSortOrder.SONG_YEAR -> return MusicUtil.getYearString(dataSet[position].year)
SortOrder.SongSortOrder.COMPOSER -> dataSet[position].composer SortOrder.SongSortOrder.COMPOSER -> dataSet[position].composer
SortOrder.SongSortOrder.SONG_ALBUM_ARTIST -> dataSet[position].albumArtist
else -> { else -> {
return "" return ""
} }

View file

@ -24,7 +24,6 @@ import android.os.Build
import code.name.monkey.retromusic.appshortcuts.shortcuttype.LastAddedShortcutType import code.name.monkey.retromusic.appshortcuts.shortcuttype.LastAddedShortcutType
import code.name.monkey.retromusic.appshortcuts.shortcuttype.ShuffleAllShortcutType import code.name.monkey.retromusic.appshortcuts.shortcuttype.ShuffleAllShortcutType
import code.name.monkey.retromusic.appshortcuts.shortcuttype.TopTracksShortcutType import code.name.monkey.retromusic.appshortcuts.shortcuttype.TopTracksShortcutType
import java.util.*
@TargetApi(Build.VERSION_CODES.N_MR1) @TargetApi(Build.VERSION_CODES.N_MR1)
class DynamicShortcutManager(private val context: Context) { class DynamicShortcutManager(private val context: Context) {

View file

@ -27,15 +27,16 @@ import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.MainActivity import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
import code.name.monkey.retromusic.glide.SongGlideRequest import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.service.MusicService.* import code.name.monkey.retromusic.service.MusicService.*
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil
import com.bumptech.glide.Glide 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.SimpleTarget
import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.target.Target
import com.bumptech.glide.request.transition.Transition
class AppWidgetBig : BaseAppWidget() { class AppWidgetBig : BaseAppWidget() {
private var target: Target<Bitmap>? = null // for cancellation private var target: Target<Bitmap>? = null // for cancellation
@ -158,20 +159,22 @@ class AppWidgetBig : BaseAppWidget() {
val appContext = service.applicationContext val appContext = service.applicationContext
service.runOnUiThread { service.runOnUiThread {
if (target != null) { if (target != null) {
Glide.clear(target) Glide.with(service).clear(target)
} }
target = SongGlideRequest.Builder.from(Glide.with(appContext), song) target = GlideApp.with(appContext)
.checkIgnoreMediaStore(appContext).asBitmap().build() .asBitmap()
//.checkIgnoreMediaStore()
.load(RetroGlideExtension.getSongModel(song))
.into(object : SimpleTarget<Bitmap>(widgetImageSize, widgetImageSize) { .into(object : SimpleTarget<Bitmap>(widgetImageSize, widgetImageSize) {
override fun onResourceReady( override fun onResourceReady(
resource: Bitmap, resource: Bitmap,
glideAnimation: GlideAnimation<in Bitmap> transition: Transition<in Bitmap>?
) { ) {
update(resource) update(resource)
} }
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) { override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(e, errorDrawable) super.onLoadFailed(errorDrawable)
update(null) update(null)
} }

View file

@ -27,7 +27,8 @@ import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.MainActivity import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
import code.name.monkey.retromusic.glide.SongGlideRequest import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.service.MusicService.* import code.name.monkey.retromusic.service.MusicService.*
@ -35,9 +36,9 @@ import code.name.monkey.retromusic.util.ImageUtil
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil
import com.bumptech.glide.Glide 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.SimpleTarget
import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.target.Target
import com.bumptech.glide.request.transition.Transition
class AppWidgetCard : BaseAppWidget() { class AppWidgetCard : BaseAppWidget() {
private var target: Target<BitmapPaletteWrapper>? = null // for cancellation private var target: Target<BitmapPaletteWrapper>? = null // for cancellation
@ -150,14 +151,15 @@ class AppWidgetCard : BaseAppWidget() {
// Load the album cover async and push the update on completion // Load the album cover async and push the update on completion
service.runOnUiThread { service.runOnUiThread {
if (target != null) { if (target != null) {
Glide.clear(target) Glide.with(service).clear(target)
} }
target = SongGlideRequest.Builder.from(Glide.with(service), song) target = GlideApp.with(service).asBitmapPalette().songCoverOptions(song)
.checkIgnoreMediaStore(service).generatePalette(service).build().centerCrop() .load(RetroGlideExtension.getSongModel(song))
.centerCrop()
.into(object : SimpleTarget<BitmapPaletteWrapper>(imageSize, imageSize) { .into(object : SimpleTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
override fun onResourceReady( override fun onResourceReady(
resource: BitmapPaletteWrapper, resource: BitmapPaletteWrapper,
glideAnimation: GlideAnimation<in BitmapPaletteWrapper> transition: Transition<in BitmapPaletteWrapper>?
) { ) {
val palette = resource.palette val palette = resource.palette
update( update(
@ -171,8 +173,8 @@ class AppWidgetCard : BaseAppWidget() {
) )
} }
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) { override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(e, errorDrawable) super.onLoadFailed(errorDrawable)
update(null, MaterialValueHelper.getSecondaryTextColor(service, true)) update(null, MaterialValueHelper.getSecondaryTextColor(service, true))
} }

View file

@ -28,7 +28,8 @@ import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.MainActivity import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
import code.name.monkey.retromusic.glide.SongGlideRequest import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.service.MusicService.* import code.name.monkey.retromusic.service.MusicService.*
@ -36,9 +37,9 @@ import code.name.monkey.retromusic.util.ImageUtil
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil
import com.bumptech.glide.Glide 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.SimpleTarget
import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.target.Target
import com.bumptech.glide.request.transition.Transition
class AppWidgetClassic : BaseAppWidget() { class AppWidgetClassic : BaseAppWidget() {
private var target: Target<BitmapPaletteWrapper>? = null // for cancellation private var target: Target<BitmapPaletteWrapper>? = null // for cancellation
@ -120,14 +121,16 @@ class AppWidgetClassic : BaseAppWidget() {
val appContext = service.applicationContext val appContext = service.applicationContext
service.runOnUiThread { service.runOnUiThread {
if (target != null) { if (target != null) {
Glide.clear(target) Glide.with(service).clear(target)
} }
target = SongGlideRequest.Builder.from(Glide.with(service), song) target = GlideApp.with(service).asBitmapPalette().songCoverOptions(song)
.checkIgnoreMediaStore(service).generatePalette(service).build().centerCrop() .load(RetroGlideExtension.getSongModel(song))
//.checkIgnoreMediaStore()
.centerCrop()
.into(object : SimpleTarget<BitmapPaletteWrapper>(imageSize, imageSize) { .into(object : SimpleTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
override fun onResourceReady( override fun onResourceReady(
resource: BitmapPaletteWrapper, resource: BitmapPaletteWrapper,
glideAnimation: GlideAnimation<in BitmapPaletteWrapper> transition: Transition<in BitmapPaletteWrapper>?
) { ) {
val palette = resource.palette val palette = resource.palette
update( update(
@ -143,8 +146,8 @@ class AppWidgetClassic : BaseAppWidget() {
) )
} }
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) { override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(e, errorDrawable) super.onLoadFailed(errorDrawable)
update(null, Color.WHITE) update(null, Color.WHITE)
} }

View file

@ -27,16 +27,17 @@ import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.MainActivity import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
import code.name.monkey.retromusic.glide.SongGlideRequest import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.service.MusicService.* import code.name.monkey.retromusic.service.MusicService.*
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil
import com.bumptech.glide.Glide 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.SimpleTarget
import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.target.Target
import com.bumptech.glide.request.transition.Transition
class AppWidgetSmall : BaseAppWidget() { class AppWidgetSmall : BaseAppWidget() {
private var target: Target<BitmapPaletteWrapper>? = null // for cancellation private var target: Target<BitmapPaletteWrapper>? = null // for cancellation
@ -123,14 +124,16 @@ class AppWidgetSmall : BaseAppWidget() {
val appContext = service.applicationContext val appContext = service.applicationContext
service.runOnUiThread { service.runOnUiThread {
if (target != null) { if (target != null) {
Glide.clear(target) Glide.with(service).clear(target)
} }
target = SongGlideRequest.Builder.from(Glide.with(service), song) target = GlideApp.with(service).asBitmapPalette().songCoverOptions(song)
.checkIgnoreMediaStore(service).generatePalette(service).build().centerCrop() //.checkIgnoreMediaStore()
.load(RetroGlideExtension.getSongModel(song))
.centerCrop()
.into(object : SimpleTarget<BitmapPaletteWrapper>(imageSize, imageSize) { .into(object : SimpleTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
override fun onResourceReady( override fun onResourceReady(
resource: BitmapPaletteWrapper, resource: BitmapPaletteWrapper,
glideAnimation: GlideAnimation<in BitmapPaletteWrapper> transition: Transition<in BitmapPaletteWrapper>?
) { ) {
val palette = resource.palette val palette = resource.palette
update( update(
@ -144,8 +147,8 @@ class AppWidgetSmall : BaseAppWidget() {
) )
} }
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) { override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(e, errorDrawable) super.onLoadFailed(errorDrawable)
update(null, MaterialValueHelper.getSecondaryTextColor(service, true)) update(null, MaterialValueHelper.getSecondaryTextColor(service, true))
} }

View file

@ -0,0 +1,101 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.auto;
import androidx.annotation.NonNull;
/**
* Created by Beesham Sarendranauth (Beesham)
*/
public class AutoMediaIDHelper {
// Media IDs used on browseable items of MediaBrowser
public static final String MEDIA_ID_EMPTY_ROOT = "__EMPTY_ROOT__";
public static final String MEDIA_ID_ROOT = "__ROOT__";
public static final String MEDIA_ID_MUSICS_BY_SEARCH = "__BY_SEARCH__"; // TODO
public static final String MEDIA_ID_MUSICS_BY_HISTORY = "__BY_HISTORY__";
public static final String MEDIA_ID_MUSICS_BY_TOP_TRACKS = "__BY_TOP_TRACKS__";
public static final String MEDIA_ID_MUSICS_BY_SUGGESTIONS = "__BY_SUGGESTIONS__";
public static final String MEDIA_ID_MUSICS_BY_PLAYLIST = "__BY_PLAYLIST__";
public static final String MEDIA_ID_MUSICS_BY_ALBUM = "__BY_ALBUM__";
public static final String MEDIA_ID_MUSICS_BY_ARTIST = "__BY_ARTIST__";
public static final String MEDIA_ID_MUSICS_BY_ALBUM_ARTIST = "__BY_ALBUM_ARTIST__";
public static final String MEDIA_ID_MUSICS_BY_GENRE = "__BY_GENRE__";
public static final String MEDIA_ID_MUSICS_BY_SHUFFLE = "__BY_SHUFFLE__";
public static final String MEDIA_ID_MUSICS_BY_QUEUE = "__BY_QUEUE__";
private static final String CATEGORY_SEPARATOR = "__/__";
private static final String LEAF_SEPARATOR = "__|__";
/**
* Create a String value that represents a playable or a browsable media.
* <p/>
* Encode the media browseable categories, if any, and the unique music ID, if any,
* into a single String mediaID.
* <p/>
* MediaIDs are of the form <categoryType>__/__<categoryValue>__|__<musicUniqueId>, 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));
}
}

View file

@ -0,0 +1,285 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.auto
import android.content.Context
import android.content.res.Resources
import android.net.Uri
import android.support.v4.media.MediaBrowserCompat
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.model.CategoryInfo
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.repository.*
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import java.lang.ref.WeakReference
import java.util.*
/**
* Created by Beesham Sarendranauth (Beesham)
*/
class AutoMusicProvider(
val mContext: Context,
private val songsRepository: SongRepository,
private val albumsRepository: AlbumRepository,
private val artistsRepository: ArtistRepository,
private val genresRepository: GenreRepository,
private val playlistsRepository: PlaylistRepository,
private val topPlayedRepository: TopPlayedRepository
) {
private var mMusicService: WeakReference<MusicService>? = null
fun setMusicService(service: MusicService) {
mMusicService = WeakReference(service)
}
fun getChildren(mediaId: String?, resources: Resources): List<MediaBrowserCompat.MediaItem> {
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = 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<MediaBrowserCompat.MediaItem>
) {
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<MediaBrowserCompat.MediaItem> {
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = 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()
}
}

View file

@ -0,0 +1,102 @@
package code.name.monkey.retromusic.auto
import android.content.Context
import android.net.Uri
import android.os.Bundle
import android.support.v4.media.MediaBrowserCompat
import android.support.v4.media.MediaDescriptionCompat
import code.name.monkey.retromusic.util.ImageUtil
internal object AutoMediaItem {
fun with(context: Context): Builder {
return Builder(context)
}
internal class Builder(val mContext: Context) {
private var mBuilder: MediaDescriptionCompat.Builder?
private var mFlags = 0
fun path(fullPath: String): Builder {
mBuilder?.setMediaId(fullPath)
return this
}
fun path(path: String?, id: Long): Builder {
return path(AutoMediaIDHelper.createMediaID(id.toString(), path))
}
fun title(title: String): Builder {
mBuilder?.setTitle(title)
return this
}
fun subTitle(subTitle: String): Builder {
mBuilder?.setSubtitle(subTitle)
return this
}
fun icon(uri: Uri?): Builder {
mBuilder?.setIconUri(uri)
return this
}
fun icon(iconDrawableId: Int): Builder {
mBuilder?.setIconBitmap(
ImageUtil.createBitmap(
ImageUtil.getVectorDrawable(
mContext.resources,
iconDrawableId,
mContext.theme
)
)
)
return this
}
fun gridLayout(isGrid: Boolean): Builder {
val hints = Bundle()
hints.putBoolean(CONTENT_STYLE_SUPPORTED, true)
hints.putInt(
CONTENT_STYLE_BROWSABLE_HINT,
if (isGrid) CONTENT_STYLE_GRID_ITEM_HINT_VALUE else CONTENT_STYLE_LIST_ITEM_HINT_VALUE
)
hints.putInt(
CONTENT_STYLE_PLAYABLE_HINT,
if (isGrid) CONTENT_STYLE_GRID_ITEM_HINT_VALUE else CONTENT_STYLE_LIST_ITEM_HINT_VALUE
)
mBuilder?.setExtras(hints)
return this
}
fun asBrowsable(): Builder {
mFlags = mFlags or MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
return this
}
fun asPlayable(): Builder {
mFlags = mFlags or MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
return this
}
fun build(): MediaBrowserCompat.MediaItem {
val result = MediaBrowserCompat.MediaItem(mBuilder!!.build(), mFlags)
mBuilder = null
mFlags = 0
return result
}
init {
mBuilder = MediaDescriptionCompat.Builder()
}
companion object{
// Hints - see https://developer.android.com/training/cars/media#default-content-style
const val CONTENT_STYLE_SUPPORTED = "android.media.browse.CONTENT_STYLE_SUPPORTED"
const val CONTENT_STYLE_BROWSABLE_HINT = "android.media.browse.CONTENT_STYLE_BROWSABLE_HINT"
const val CONTENT_STYLE_PLAYABLE_HINT = "android.media.browse.CONTENT_STYLE_PLAYABLE_HINT"
const val CONTENT_STYLE_LIST_ITEM_HINT_VALUE = 1
const val CONTENT_STYLE_GRID_ITEM_HINT_VALUE = 2
}
}
}

View file

@ -0,0 +1,83 @@
package code.name.monkey.retromusic.cast
import androidx.core.net.toUri
import code.name.monkey.retromusic.cast.RetroWebServer.Companion.MIME_TYPE_AUDIO
import code.name.monkey.retromusic.cast.RetroWebServer.Companion.PART_COVER_ART
import code.name.monkey.retromusic.cast.RetroWebServer.Companion.PART_SONG
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.RetroUtil
import com.google.android.gms.cast.*
import com.google.android.gms.cast.MediaInfo.STREAM_TYPE_BUFFERED
import com.google.android.gms.cast.MediaMetadata.*
import com.google.android.gms.cast.framework.CastSession
import com.google.android.gms.common.images.WebImage
import java.net.MalformedURLException
import java.net.URL
object CastHelper {
private const val CAST_MUSIC_METADATA_ID = "metadata_id"
private const val CAST_MUSIC_METADATA_ALBUM_ID = "metadata_album_id"
private const val CAST_URL_PROTOCOL = "http"
fun castSong(castSession: CastSession, song: Song) {
try {
val remoteMediaClient = castSession.remoteMediaClient
val mediaLoadOptions = MediaLoadOptions.Builder().apply {
setPlayPosition(0)
setAutoplay(true)
}.build()
remoteMediaClient?.load(song.toMediaInfo()!!, mediaLoadOptions)
} catch (e: Exception) {
e.printStackTrace()
}
}
fun castQueue(castSession: CastSession, songs: List<Song>, position: Int, progress: Long) {
try {
val remoteMediaClient = castSession.remoteMediaClient
remoteMediaClient?.queueLoad(
songs.toMediaInfoList(),
position,
MediaStatus.REPEAT_MODE_REPEAT_OFF,
progress,
null
)
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun List<Song>.toMediaInfoList(): Array<MediaQueueItem> {
return map { MediaQueueItem.Builder(it.toMediaInfo()!!).build() }.toTypedArray()
}
private fun Song.toMediaInfo(): MediaInfo? {
val song = this
val baseUrl: URL
try {
baseUrl = URL(CAST_URL_PROTOCOL, RetroUtil.getIpAddress(true), SERVER_PORT, "")
} catch (e: MalformedURLException) {
return null
}
val songUrl = "$baseUrl/$PART_SONG?id=${song.id}"
val albumArtUrl = "$baseUrl/$PART_COVER_ART?id=${song.albumId}"
val musicMetadata = MediaMetadata(MEDIA_TYPE_MUSIC_TRACK).apply {
putInt(CAST_MUSIC_METADATA_ID, song.id.toInt())
putInt(CAST_MUSIC_METADATA_ALBUM_ID, song.albumId.toInt())
putString(KEY_TITLE, song.title)
putString(KEY_ARTIST, song.artistName)
putString(KEY_ALBUM_TITLE, song.albumName)
putInt(KEY_TRACK_NUMBER, song.trackNumber)
addImage(WebImage(albumArtUrl.toUri()))
}
return MediaInfo.Builder(songUrl).apply {
setStreamType(STREAM_TYPE_BUFFERED)
setContentType(MIME_TYPE_AUDIO)
setMetadata(musicMetadata)
setStreamDuration(song.duration)
}.build()
}
}

View file

@ -0,0 +1,41 @@
package code.name.monkey.retromusic.cast
import android.content.Context
import com.google.android.gms.cast.CastMediaControlIntent
import com.google.android.gms.cast.framework.CastOptions
import com.google.android.gms.cast.framework.OptionsProvider
import com.google.android.gms.cast.framework.SessionProvider
import com.google.android.gms.cast.framework.media.CastMediaOptions
import com.google.android.gms.cast.framework.media.MediaIntentReceiver
import com.google.android.gms.cast.framework.media.NotificationOptions
import java.util.*
class CastOptionsProvider : OptionsProvider {
override fun getCastOptions(context: Context): CastOptions {
val buttonActions: MutableList<String> = ArrayList()
buttonActions.add(MediaIntentReceiver.ACTION_SKIP_PREV)
buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK)
buttonActions.add(MediaIntentReceiver.ACTION_SKIP_NEXT)
buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING)
val compatButtonActionsIndices = intArrayOf(1, 3)
val notificationOptions = NotificationOptions.Builder()
.setActions(buttonActions, compatButtonActionsIndices)
.setTargetActivityClassName(ExpandedControlsActivity::class.java.name)
.build()
val mediaOptions = CastMediaOptions.Builder()
.setNotificationOptions(notificationOptions)
.setExpandedControllerActivityClassName(ExpandedControlsActivity::class.java.name)
.build()
return CastOptions.Builder()
.setReceiverApplicationId(CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID)
.setCastMediaOptions(mediaOptions)
.build()
}
override fun getAdditionalSessionProviders(context: Context?): List<SessionProvider>? {
return null
}
}

View file

@ -0,0 +1,19 @@
package code.name.monkey.retromusic.cast
import android.view.Menu
import code.name.monkey.retromusic.R
import com.google.android.gms.cast.framework.CastButtonFactory
import com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity
class ExpandedControlsActivity : ExpandedControllerActivity() {
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
super.onCreateOptionsMenu(menu)
menuInflater.inflate(R.menu.menu_cast, menu)
CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.action_cast)
return true
}
}

View file

@ -0,0 +1,22 @@
package code.name.monkey.retromusic.cast
import com.google.android.gms.cast.framework.CastSession
import com.google.android.gms.cast.framework.SessionManagerListener
interface RetroSessionManager : SessionManagerListener<CastSession> {
override fun onSessionResuming(p0: CastSession, p1: String) {
}
override fun onSessionStartFailed(p0: CastSession, p1: Int) {
}
override fun onSessionResumeFailed(p0: CastSession, p1: Int) {
}
override fun onSessionEnding(p0: CastSession) {
}
}

View file

@ -0,0 +1,140 @@
package code.name.monkey.retromusic.cast
import android.content.Context
import code.name.monkey.retromusic.util.MusicUtil
import fi.iki.elonen.NanoHTTPD
import fi.iki.elonen.NanoHTTPD.Response.Status
import java.io.*
const val SERVER_PORT = 9090
class RetroWebServer(val context: Context) : NanoHTTPD(SERVER_PORT) {
companion object {
private const val MIME_TYPE_IMAGE = "image/jpg"
const val MIME_TYPE_AUDIO = "audio/mp3"
const val PART_COVER_ART = "coverart"
const val PART_SONG = "song"
const val PARAM_ID = "id"
var mRetroWebServer: RetroWebServer? = null
fun getInstance(context: Context): RetroWebServer {
if (mRetroWebServer == null) {
mRetroWebServer = RetroWebServer(context)
}
return mRetroWebServer!!
}
}
override fun serve(
uri: String?,
method: Method?,
headers: MutableMap<String, String>?,
parms: MutableMap<String, String>?,
files: MutableMap<String, String>?
): Response {
if (uri?.contains(PART_COVER_ART) == true) {
val albumId = parms?.get(PARAM_ID) ?: return errorResponse()
val albumArtUri = MusicUtil.getMediaStoreAlbumCoverUri(albumId.toLong())
val fis: InputStream?
try {
fis = context.contentResolver.openInputStream(albumArtUri)
} catch (e: FileNotFoundException) {
return errorResponse()
}
return newChunkedResponse(Status.OK, MIME_TYPE_IMAGE, fis)
} else if (uri?.contains(PART_SONG) == true) {
val songId = parms?.get(PARAM_ID) ?: return errorResponse()
val songUri = MusicUtil.getSongFileUri(songId.toLong())
val songPath = MusicUtil.getSongFilePath(context, songUri)
val song = File(songPath)
return serveFile(headers!!, song, MIME_TYPE_AUDIO)
}
return newFixedLengthResponse(Status.NOT_FOUND, MIME_PLAINTEXT, "Not Found")
}
private fun serveFile(
header: MutableMap<String, String>, file: File,
mime: String
): Response {
var res: Response
try {
// Support (simple) skipping:
var startFrom: Long = 0
var endAt: Long = -1
// The value of header range will be bytes=0-1024 something like this
// We get the value of from Bytes i.e. startFrom and toBytes i.e. endAt below
var range = header["range"]
if (range != null) {
if (range.startsWith("bytes=")) {
range = range.substring("bytes=".length)
val minus = range.indexOf('-')
try {
if (minus > 0) {
startFrom = range
.substring(0, minus).toLong()
endAt = range.substring(minus + 1).toLong()
}
} catch (ignored: NumberFormatException) {
}
}
}
// Chunked Response is used when serving audio file
// Change return code and add Content-Range header when skipping is
// requested
val fileLen = file.length()
if (range != null && startFrom >= 0) {
if (startFrom >= fileLen) {
res = newFixedLengthResponse(
Status.RANGE_NOT_SATISFIABLE,
MIME_PLAINTEXT, ""
)
res.addHeader("Content-Range", "bytes 0-0/$fileLen")
} else {
if (endAt < 0) {
endAt = fileLen - 1
}
var newLen = endAt - startFrom + 1
if (newLen < 0) {
newLen = 0
}
val dataLen = newLen
val fis: FileInputStream = object : FileInputStream(file) {
@Throws(IOException::class)
override fun available(): Int {
return dataLen.toInt()
}
}
fis.skip(startFrom)
res = newChunkedResponse(
Status.PARTIAL_CONTENT, mime,
fis
)
res.addHeader("Content-Length", "" + dataLen)
res.addHeader(
"Content-Range", "bytes " + startFrom + "-"
+ endAt + "/" + fileLen
)
}
} else {
res = newFixedLengthResponse(
Status.OK, mime,
FileInputStream(file), file.length()
)
res.addHeader("Accept-Ranges", "bytes")
res.addHeader("Content-Length", "" + fileLen)
}
} catch (ioe: IOException) {
res = newFixedLengthResponse(
Status.FORBIDDEN,
MIME_PLAINTEXT, "FORBIDDEN: Reading file failed."
)
}
return res
}
private fun errorResponse(message: String = "Error Occurred"): Response {
return newFixedLengthResponse(Status.INTERNAL_ERROR, MIME_PLAINTEXT, message)
}
}

View file

@ -47,7 +47,7 @@ interface PlaylistDao {
@Query("SELECT * FROM SongEntity WHERE playlist_creator_id = :playlistId AND id = :songId") @Query("SELECT * FROM SongEntity WHERE playlist_creator_id = :playlistId AND id = :songId")
suspend fun isSongExistsInPlaylist(playlistId: Long, songId: Long): List<SongEntity> suspend fun isSongExistsInPlaylist(playlistId: Long, songId: Long): List<SongEntity>
@Query("SELECT * FROM SongEntity WHERE playlist_creator_id = :playlistId") @Query("SELECT * FROM SongEntity WHERE playlist_creator_id = :playlistId ORDER BY song_key asc")
fun songsFromPlaylist(playlistId: Long): LiveData<List<SongEntity>> fun songsFromPlaylist(playlistId: Long): LiveData<List<SongEntity>>
@Delete @Delete

View file

@ -18,7 +18,7 @@ import android.os.Parcelable
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import kotlinx.android.parcel.Parcelize import kotlinx.parcelize.Parcelize
@Entity @Entity
@Parcelize @Parcelize

View file

@ -17,7 +17,7 @@ package code.name.monkey.retromusic.db
import android.os.Parcelable import android.os.Parcelable
import androidx.room.Embedded import androidx.room.Embedded
import androidx.room.Relation import androidx.room.Relation
import kotlinx.android.parcel.Parcelize import kotlinx.parcelize.Parcelize
@Parcelize @Parcelize
data class PlaylistWithSongs( data class PlaylistWithSongs(

View file

@ -19,7 +19,7 @@ import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.Index import androidx.room.Index
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import kotlinx.android.parcel.Parcelize import kotlinx.parcelize.Parcelize
@Parcelize @Parcelize
@Entity(indices = [Index(value = ["playlist_creator_id", "id"], unique = true)]) @Entity(indices = [Index(value = ["playlist_creator_id", "id"], unique = true)])

View file

@ -1,155 +0,0 @@
package code.name.monkey.retromusic.dialogs;
import android.Manifest;
import android.app.Dialog;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.DialogFragment;
import code.name.monkey.retromusic.R;
import com.afollestad.materialdialogs.MaterialDialog;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class BlacklistFolderChooserDialog extends DialogFragment
implements MaterialDialog.ListCallback {
String initialPath = Environment.getExternalStorageDirectory().getAbsolutePath();
private File parentFolder;
private File[] parentContents;
private boolean canGoUp = false;
private FolderCallback callback;
public static BlacklistFolderChooserDialog create() {
return new BlacklistFolderChooserDialog();
}
private String[] getContentsArray() {
if (parentContents == null) {
if (canGoUp) {
return new String[] {".."};
}
return new String[] {};
}
String[] results = new String[parentContents.length + (canGoUp ? 1 : 0)];
if (canGoUp) {
results[0] = "..";
}
for (int i = 0; i < parentContents.length; i++) {
results[canGoUp ? i + 1 : i] = parentContents[i].getName();
}
return results;
}
private File[] listFiles() {
File[] contents = parentFolder.listFiles();
List<File> results = new ArrayList<>();
if (contents != null) {
for (File fi : contents) {
if (fi.isDirectory()) {
results.add(fi);
}
}
Collections.sort(results, new FolderSorter());
return results.toArray(new File[results.size()]);
}
return null;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& ActivityCompat.checkSelfPermission(
requireActivity(), Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
return new MaterialDialog.Builder(requireActivity())
.title(R.string.md_error_label)
.content(R.string.md_storage_perm_error)
.positiveText(android.R.string.ok)
.build();
}
if (savedInstanceState == null) {
savedInstanceState = new Bundle();
}
if (!savedInstanceState.containsKey("current_path")) {
savedInstanceState.putString("current_path", initialPath);
}
parentFolder = new File(savedInstanceState.getString("current_path", File.pathSeparator));
checkIfCanGoUp();
parentContents = listFiles();
MaterialDialog.Builder builder =
new MaterialDialog.Builder(requireContext())
.title(parentFolder.getAbsolutePath())
.items((CharSequence[]) getContentsArray())
.itemsCallback(this)
.autoDismiss(false)
.onPositive(
(dialog, which) -> {
callback.onFolderSelection(BlacklistFolderChooserDialog.this, parentFolder);
dismiss();
})
.onNegative((materialDialog, dialogAction) -> dismiss())
.positiveText(R.string.add_action)
.negativeText(android.R.string.cancel);
return builder.build();
}
@Override
public void onSelection(MaterialDialog materialDialog, View view, int i, CharSequence s) {
if (canGoUp && i == 0) {
parentFolder = parentFolder.getParentFile();
if (parentFolder.getAbsolutePath().equals("/storage/emulated")) {
parentFolder = parentFolder.getParentFile();
}
checkIfCanGoUp();
} else {
parentFolder = parentContents[canGoUp ? i - 1 : i];
canGoUp = true;
if (parentFolder.getAbsolutePath().equals("/storage/emulated")) {
parentFolder = Environment.getExternalStorageDirectory();
}
}
reload();
}
private void checkIfCanGoUp() {
canGoUp = parentFolder.getParent() != null;
}
private void reload() {
parentContents = listFiles();
MaterialDialog dialog = (MaterialDialog) getDialog();
dialog.setTitle(parentFolder.getAbsolutePath());
dialog.setItems((CharSequence[]) getContentsArray());
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("current_path", parentFolder.getAbsolutePath());
}
public void setCallback(FolderCallback callback) {
this.callback = callback;
}
public interface FolderCallback {
void onFolderSelection(@NonNull BlacklistFolderChooserDialog dialog, @NonNull File folder);
}
private static class FolderSorter implements Comparator<File> {
@Override
public int compare(File lhs, File rhs) {
return lhs.getName().compareTo(rhs.getName());
}
}
}

View file

@ -0,0 +1,152 @@
package code.name.monkey.retromusic.dialogs
import android.Manifest
import android.app.Dialog
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.os.Environment
import androidx.core.app.ActivityCompat
import androidx.fragment.app.DialogFragment
import code.name.monkey.retromusic.R
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItems
import com.afollestad.materialdialogs.list.updateListItems
import java.io.File
import java.util.*
class BlacklistFolderChooserDialog : DialogFragment() {
private var initialPath: String = Environment.getExternalStorageDirectory().absolutePath
private var parentFolder: File? = null
private var parentContents: Array<File>? = null
private var canGoUp = false
private var callback: FolderCallback? = null
private val contentsArray: Array<String?>
get() {
if (parentContents == null) {
return if (canGoUp) {
arrayOf("..")
} else arrayOf()
}
val results = arrayOfNulls<String>(parentContents!!.size + if (canGoUp) 1 else 0)
if (canGoUp) {
results[0] = ".."
}
for (i in parentContents!!.indices) {
results[if (canGoUp) i + 1 else i] = parentContents!![i].name
}
return results
}
private fun listFiles(): Array<File>? {
val contents = parentFolder!!.listFiles()
val results: MutableList<File> = ArrayList()
if (contents != null) {
for (fi in contents) {
if (fi.isDirectory) {
results.add(fi)
}
}
Collections.sort(results, FolderSorter())
return results.toTypedArray()
}
return null
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
var mSavedInstanceState = savedInstanceState
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& ActivityCompat.checkSelfPermission(
requireActivity(), Manifest.permission.READ_EXTERNAL_STORAGE
)
!= PackageManager.PERMISSION_GRANTED
) {
return MaterialDialog(requireActivity()).show {
title(res = R.string.md_error_label)
message(res = R.string.md_storage_perm_error)
positiveButton(res = android.R.string.ok)
}
}
if (mSavedInstanceState == null) {
mSavedInstanceState = Bundle()
}
if (!mSavedInstanceState.containsKey("current_path")) {
mSavedInstanceState.putString("current_path", initialPath)
}
parentFolder = File(mSavedInstanceState.getString("current_path", File.pathSeparator))
checkIfCanGoUp()
parentContents = listFiles()
return MaterialDialog(requireContext())
.title(text = parentFolder!!.absolutePath)
.listItems(
items = contentsArray.toCharSequence(),
waitForPositiveButton = false
) { _: MaterialDialog, i: Int, _: CharSequence ->
onSelection(i)
}
.noAutoDismiss()
.cornerRadius(literalDp = 20F)
.positiveButton(res = R.string.add_action) {
callback!!.onFolderSelection(this@BlacklistFolderChooserDialog, parentFolder!!)
dismiss()
}
.negativeButton(res = android.R.string.cancel) { dismiss() }
}
private fun onSelection(i: Int) {
if (canGoUp && i == 0) {
parentFolder = parentFolder!!.parentFile
if (parentFolder!!.absolutePath == "/storage/emulated") {
parentFolder = parentFolder!!.parentFile
}
checkIfCanGoUp()
} else {
parentFolder = parentContents!![if (canGoUp) i - 1 else i]
canGoUp = true
if (parentFolder!!.absolutePath == "/storage/emulated") {
parentFolder = Environment.getExternalStorageDirectory()
}
}
reload()
}
private fun checkIfCanGoUp() {
canGoUp = parentFolder!!.parent != null
}
private fun reload() {
parentContents = listFiles()
val dialog = dialog as MaterialDialog?
dialog!!.setTitle(parentFolder!!.absolutePath)
dialog.updateListItems(items = contentsArray.toCharSequence())
}
private fun Array<String?>.toCharSequence(): List<CharSequence> {
return map { it as CharSequence }
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString("current_path", parentFolder!!.absolutePath)
}
fun setCallback(callback: FolderCallback?) {
this.callback = callback
}
interface FolderCallback {
fun onFolderSelection(dialog: BlacklistFolderChooserDialog, folder: File)
}
private class FolderSorter : Comparator<File> {
override fun compare(lhs: File, rhs: File): Int {
return lhs.name.compareTo(rhs.name)
}
}
companion object {
fun create(): BlacklistFolderChooserDialog {
return BlacklistFolderChooserDialog()
}
}
}

View file

@ -17,29 +17,23 @@ package code.name.monkey.retromusic.dialogs
import android.app.Dialog import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
import android.view.LayoutInflater
import android.widget.Toast
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import code.name.monkey.retromusic.EXTRA_SONG import code.name.monkey.retromusic.EXTRA_SONG
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.db.PlaylistEntity import code.name.monkey.retromusic.databinding.DialogPlaylistBinding
import code.name.monkey.retromusic.db.toSongEntity
import code.name.monkey.retromusic.extensions.colorButtons import code.name.monkey.retromusic.extensions.colorButtons
import code.name.monkey.retromusic.extensions.extra import code.name.monkey.retromusic.extensions.extra
import code.name.monkey.retromusic.extensions.materialDialog import code.name.monkey.retromusic.extensions.materialDialog
import code.name.monkey.retromusic.fragments.LibraryViewModel import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.fragments.ReloadType.Playlists
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import kotlinx.android.synthetic.main.dialog_playlist.view.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.koin.androidx.viewmodel.ext.android.sharedViewModel import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class CreatePlaylistDialog : DialogFragment() { class CreatePlaylistDialog : DialogFragment() {
private var _binding: DialogPlaylistBinding? = null
private val binding get() = _binding!!
private val libraryViewModel by sharedViewModel<LibraryViewModel>() private val libraryViewModel by sharedViewModel<LibraryViewModel>()
companion object { companion object {
@ -56,13 +50,15 @@ class CreatePlaylistDialog : DialogFragment() {
} }
} }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val view = LayoutInflater.from(requireActivity()).inflate(R.layout.dialog_playlist, null) _binding = DialogPlaylistBinding.inflate(layoutInflater)
val songs: List<Song> = extra<List<Song>>(EXTRA_SONG).value ?: emptyList() val songs: List<Song> = extra<List<Song>>(EXTRA_SONG).value ?: emptyList()
val playlistView: TextInputEditText = view.actionNewPlaylist val playlistView: TextInputEditText = binding.actionNewPlaylist
val playlistContainer: TextInputLayout = view.actionNewPlaylistContainer val playlistContainer: TextInputLayout = binding.actionNewPlaylistContainer
return materialDialog(R.string.new_playlist_title) return materialDialog(R.string.new_playlist_title)
.setView(view) .setView(binding.root)
.setPositiveButton( .setPositiveButton(
R.string.create_action R.string.create_action
) { _, _ -> ) { _, _ ->
@ -77,4 +73,9 @@ class CreatePlaylistDialog : DialogFragment() {
.create() .create()
.colorButtons() .colorButtons()
} }
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
} }

View file

@ -14,24 +14,31 @@
*/ */
package code.name.monkey.retromusic.dialogs package code.name.monkey.retromusic.dialogs
import android.app.Activity
import android.app.Dialog import android.app.Dialog
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import code.name.monkey.retromusic.EXTRA_SONG import code.name.monkey.retromusic.EXTRA_SONG
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.colorButtons import code.name.monkey.retromusic.activities.saf.SAFGuideActivity
import code.name.monkey.retromusic.extensions.extraNotNull import code.name.monkey.retromusic.extensions.extraNotNull
import code.name.monkey.retromusic.extensions.materialDialog
import code.name.monkey.retromusic.fragments.LibraryViewModel 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.helper.MusicPlayerRemote
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import org.koin.androidx.viewmodel.ext.android.sharedViewModel import code.name.monkey.retromusic.util.SAFUtil
import com.afollestad.materialdialogs.MaterialDialog
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.koin.androidx.viewmodel.ext.android.getViewModel
class DeleteSongsDialog : DialogFragment() { class DeleteSongsDialog : DialogFragment() {
private val libraryViewModel by sharedViewModel<LibraryViewModel>() lateinit var libraryViewModel: LibraryViewModel
companion object { companion object {
fun create(song: Song): DeleteSongsDialog { fun create(song: Song): DeleteSongsDialog {
@ -50,6 +57,7 @@ class DeleteSongsDialog : DialogFragment() {
} }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
libraryViewModel = activity?.getViewModel() as LibraryViewModel
val songs = extraNotNull<List<Song>>(EXTRA_SONG).value val songs = extraNotNull<List<Song>>(EXTRA_SONG).value
val pair = if (songs.size > 1) { val pair = if (songs.size > 1) {
Pair( Pair(
@ -69,17 +77,66 @@ class DeleteSongsDialog : DialogFragment() {
) )
} }
return materialDialog(pair.first) return MaterialDialog(requireContext())
.setMessage(pair.second) .title(pair.first)
.setCancelable(false) .message(text = pair.second)
.setPositiveButton(R.string.action_delete) { _, _ -> .noAutoDismiss()
if (songs.isNotEmpty() and (songs.size == 1) and MusicPlayerRemote.isPlaying(songs.first())) { .cornerRadius(16F)
.negativeButton(android.R.string.cancel) {
dismiss()
}
.positiveButton(R.string.action_delete) {
if ((songs.size == 1) && MusicPlayerRemote.isPlaying(songs[0])) {
MusicPlayerRemote.playNextSong() MusicPlayerRemote.playNextSong()
} }
MusicUtil.deleteTracks(requireActivity(), songs) if (!SAFUtil.isSAFRequiredForSongs(songs)) {
libraryViewModel.deleteTracks(songs) CoroutineScope(Dispatchers.IO).launch {
dismiss()
MusicUtil.deleteTracks(requireContext(), songs)
reloadTabs()
} }
.create() } else {
.colorButtons() if (SAFUtil.isSDCardAccessGranted(requireActivity())) {
deleteSongs(songs)
} else {
startActivityForResult(
Intent(requireActivity(), SAFGuideActivity::class.java),
SAFGuideActivity.REQUEST_CODE_SAF_GUIDE
)
}
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
SAFGuideActivity.REQUEST_CODE_SAF_GUIDE -> {
SAFUtil.openTreePicker(this)
}
SAFUtil.REQUEST_SAF_PICK_TREE,
SAFUtil.REQUEST_SAF_PICK_FILE -> {
if (resultCode == Activity.RESULT_OK) {
SAFUtil.saveTreeUri(requireActivity(), data)
val songs = extraNotNull<List<Song>>(EXTRA_SONG).value
deleteSongs(songs)
}
}
}
}
fun deleteSongs(songs: List<Song>) {
CoroutineScope(Dispatchers.IO).launch {
dismiss()
MusicUtil.deleteTracks(requireActivity(), songs, null, null)
reloadTabs()
}
}
fun reloadTabs() {
libraryViewModel.forceReload(ReloadType.Songs)
libraryViewModel.forceReload(ReloadType.HomeSections)
libraryViewModel.forceReload(ReloadType.Artists)
libraryViewModel.forceReload(ReloadType.Albums)
} }
} }

View file

@ -38,8 +38,9 @@ import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.service.MusicService.ACTION_PENDING_QUIT import code.name.monkey.retromusic.service.MusicService.ACTION_PENDING_QUIT
import code.name.monkey.retromusic.service.MusicService.ACTION_QUIT import code.name.monkey.retromusic.service.MusicService.ACTION_QUIT
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import com.afollestad.materialdialogs.DialogAction
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.WhichButton
import com.afollestad.materialdialogs.actions.getActionButton
class SleepTimerDialog : DialogFragment() { class SleepTimerDialog : DialogFragment() {
@ -150,14 +151,14 @@ class SleepTimerDialog : DialogFragment() {
private fun updateCancelButton() { private fun updateCancelButton() {
val musicService = MusicPlayerRemote.musicService val musicService = MusicPlayerRemote.musicService
if (musicService != null && musicService.pendingQuit) { if (musicService != null && musicService.pendingQuit) {
dialog.getActionButton(DialogAction.NEUTRAL).text = dialog.getActionButton(WhichButton.NEUTRAL).text =
dialog.context.getString(R.string.cancel_current_timer) dialog.context.getString(R.string.cancel_current_timer)
} else { } else {
dialog.getActionButton(DialogAction.NEUTRAL).text = null dialog.getActionButton(WhichButton.NEUTRAL).text = null
} }
} }
private inner class TimerUpdater() : private inner class TimerUpdater :
CountDownTimer( CountDownTimer(
PreferenceUtil.nextSleepTimerElapsedRealTime - SystemClock.elapsedRealtime(), PreferenceUtil.nextSleepTimerElapsedRealTime - SystemClock.elapsedRealtime(),
1000 1000

View file

@ -32,13 +32,13 @@ import code.name.monkey.retromusic.extensions.colorButtons
import code.name.monkey.retromusic.extensions.materialDialog import code.name.monkey.retromusic.extensions.materialDialog
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import java.io.File
import java.io.IOException
import org.jaudiotagger.audio.AudioFileIO import org.jaudiotagger.audio.AudioFileIO
import org.jaudiotagger.audio.exceptions.CannotReadException import org.jaudiotagger.audio.exceptions.CannotReadException
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException import org.jaudiotagger.audio.exceptions.ReadOnlyFileException
import org.jaudiotagger.tag.TagException import org.jaudiotagger.tag.TagException
import java.io.File
import java.io.IOException
class SongDetailDialog : DialogFragment() { class SongDetailDialog : DialogFragment() {

View file

@ -41,7 +41,6 @@ import com.google.android.material.button.MaterialButton
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.progressindicator.CircularProgressIndicator import com.google.android.material.progressindicator.CircularProgressIndicator
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
fun Int.ripAlpha(): Int { fun Int.ripAlpha(): Int {

View file

@ -18,14 +18,10 @@ import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Build
import androidx.annotation.DimenRes import androidx.annotation.DimenRes
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
import code.name.monkey.retromusic.R
fun Context.scaledDrawableResources( fun Context.scaledDrawableResources(
@DrawableRes id: Int, @DrawableRes id: Int,

View file

@ -17,6 +17,7 @@ package code.name.monkey.retromusic.fragments
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.view.doOnPreDraw
import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
@ -28,26 +29,48 @@ import code.name.monkey.retromusic.adapter.album.AlbumAdapter
import code.name.monkey.retromusic.adapter.artist.ArtistAdapter import code.name.monkey.retromusic.adapter.artist.ArtistAdapter
import code.name.monkey.retromusic.adapter.song.ShuffleButtonSongAdapter import code.name.monkey.retromusic.adapter.song.ShuffleButtonSongAdapter
import code.name.monkey.retromusic.adapter.song.SongAdapter import code.name.monkey.retromusic.adapter.song.SongAdapter
import code.name.monkey.retromusic.databinding.FragmentPlaylistDetailBinding
import code.name.monkey.retromusic.db.toSong import code.name.monkey.retromusic.db.toSong
import code.name.monkey.retromusic.extensions.dipToPix import code.name.monkey.retromusic.extensions.dipToPix
import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import code.name.monkey.retromusic.interfaces.IAlbumClickListener import code.name.monkey.retromusic.interfaces.IAlbumClickListener
import code.name.monkey.retromusic.interfaces.IArtistClickListener import code.name.monkey.retromusic.interfaces.IArtistClickListener
import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil
import kotlinx.android.synthetic.main.fragment_playlist_detail.* import com.google.android.material.transition.MaterialSharedAxis
class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_detail), class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_detail),
IArtistClickListener, IAlbumClickListener { IArtistClickListener, IAlbumClickListener {
private val args by navArgs<DetailListFragmentArgs>() private val args by navArgs<DetailListFragmentArgs>()
private var _binding: FragmentPlaylistDetailBinding? = null
private val binding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentPlaylistDetailBinding.bind(view)
when (args.type) {
TOP_ARTISTS,
RECENT_ARTISTS,
TOP_ALBUMS,
RECENT_ALBUMS,
FAVOURITES -> {
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
}
else -> {
enterTransition = MaterialSharedAxis(MaterialSharedAxis.Y, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.Y, false)
}
}
postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() }
}
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
mainActivity.setBottomBarVisibility(false) mainActivity.setSupportActionBar(binding.toolbar)
mainActivity.setSupportActionBar(toolbar) binding.progressIndicator.hide()
progressIndicator.hide()
when (args.type) { when (args.type) {
TOP_ARTISTS -> loadArtists(R.string.top_artists, TOP_ARTISTS) TOP_ARTISTS -> loadArtists(R.string.top_artists, TOP_ARTISTS)
RECENT_ARTISTS -> loadArtists(R.string.recent_artists, RECENT_ARTISTS) RECENT_ARTISTS -> loadArtists(R.string.recent_artists, RECENT_ARTISTS)
@ -59,23 +82,23 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
TOP_PLAYED_PLAYLIST -> topPlayed() TOP_PLAYED_PLAYLIST -> topPlayed()
} }
recyclerView.adapter?.registerAdapterDataObserver(object : AdapterDataObserver() { binding.recyclerView.adapter?.registerAdapterDataObserver(object : AdapterDataObserver() {
override fun onChanged() { override fun onChanged() {
super.onChanged() super.onChanged()
val height = dipToPix(52f) val height = dipToPix(52f)
recyclerView.setPadding(0, 0, 0, height.toInt()) binding.recyclerView.setPadding(0, 0, 0, height.toInt())
} }
}) })
} }
private fun lastAddedSongs() { private fun lastAddedSongs() {
toolbar.setTitle(R.string.last_added) binding.toolbar.setTitle(R.string.last_added)
val songAdapter = ShuffleButtonSongAdapter( val songAdapter = ShuffleButtonSongAdapter(
requireActivity(), requireActivity(),
mutableListOf(), mutableListOf(),
R.layout.item_list, null R.layout.item_list, null
) )
recyclerView.apply { binding.recyclerView.apply {
adapter = songAdapter adapter = songAdapter
layoutManager = linearLayoutManager() layoutManager = linearLayoutManager()
} }
@ -85,13 +108,13 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
} }
private fun topPlayed() { private fun topPlayed() {
toolbar.setTitle(R.string.my_top_tracks) binding.toolbar.setTitle(R.string.my_top_tracks)
val songAdapter = ShuffleButtonSongAdapter( val songAdapter = ShuffleButtonSongAdapter(
requireActivity(), requireActivity(),
mutableListOf(), mutableListOf(),
R.layout.item_list, null R.layout.item_list, null
) )
recyclerView.apply { binding.recyclerView.apply {
adapter = songAdapter adapter = songAdapter
layoutManager = linearLayoutManager() layoutManager = linearLayoutManager()
} }
@ -101,14 +124,14 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
} }
private fun loadHistory() { private fun loadHistory() {
toolbar.setTitle(R.string.history) binding.toolbar.setTitle(R.string.history)
val songAdapter = ShuffleButtonSongAdapter( val songAdapter = ShuffleButtonSongAdapter(
requireActivity(), requireActivity(),
mutableListOf(), mutableListOf(),
R.layout.item_list, null R.layout.item_list, null
) )
recyclerView.apply { binding.recyclerView.apply {
adapter = songAdapter adapter = songAdapter
layoutManager = linearLayoutManager() layoutManager = linearLayoutManager()
} }
@ -118,13 +141,13 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
} }
private fun loadFavorite() { private fun loadFavorite() {
toolbar.setTitle(R.string.favorites) binding.toolbar.setTitle(R.string.favorites)
val songAdapter = SongAdapter( val songAdapter = SongAdapter(
requireActivity(), requireActivity(),
mutableListOf(), mutableListOf(),
R.layout.item_list, null R.layout.item_list, null
) )
recyclerView.apply { binding.recyclerView.apply {
adapter = songAdapter adapter = songAdapter
layoutManager = linearLayoutManager() layoutManager = linearLayoutManager()
} }
@ -135,9 +158,9 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
} }
private fun loadArtists(title: Int, type: Int) { private fun loadArtists(title: Int, type: Int) {
toolbar.setTitle(title) binding.toolbar.setTitle(title)
libraryViewModel.artists(type).observe(viewLifecycleOwner, { artists -> libraryViewModel.artists(type).observe(viewLifecycleOwner, { artists ->
recyclerView.apply { binding.recyclerView.apply {
adapter = artistAdapter(artists) adapter = artistAdapter(artists)
layoutManager = gridLayoutManager() layoutManager = gridLayoutManager()
} }
@ -145,9 +168,9 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
} }
private fun loadAlbums(title: Int, type: Int) { private fun loadAlbums(title: Int, type: Int) {
toolbar.setTitle(title) binding.toolbar.setTitle(title)
libraryViewModel.albums(type).observe(viewLifecycleOwner, { albums -> libraryViewModel.albums(type).observe(viewLifecycleOwner, { albums ->
recyclerView.apply { binding.recyclerView.apply {
adapter = albumAdapter(albums) adapter = albumAdapter(albums)
layoutManager = gridLayoutManager() layoutManager = gridLayoutManager()
} }
@ -186,7 +209,7 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
R.id.artistDetailsFragment, R.id.artistDetailsFragment,
bundleOf(EXTRA_ARTIST_ID to artistId), bundleOf(EXTRA_ARTIST_ID to artistId),
null, null,
FragmentNavigatorExtras(view to "artist") FragmentNavigatorExtras(view to artistId.toString())
) )
} }
@ -196,8 +219,13 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
bundleOf(EXTRA_ALBUM_ID to albumId), bundleOf(EXTRA_ALBUM_ID to albumId),
null, null,
FragmentNavigatorExtras( FragmentNavigatorExtras(
view to "album" view to albumId.toString()
) )
) )
} }
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
} }

View file

@ -25,9 +25,7 @@ import code.name.monkey.retromusic.model.*
import code.name.monkey.retromusic.repository.RealRepository import code.name.monkey.retromusic.repository.RealRepository
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class LibraryViewModel( class LibraryViewModel(
private val repository: RealRepository private val repository: RealRepository
@ -129,16 +127,16 @@ class LibraryViewModel(
} }
} }
private fun fetchHomeSections() { fun fetchHomeSections() {
viewModelScope.launch(IO) { viewModelScope.launch(IO) {
home.postValue(repository.homeSections()) home.postValue(repository.homeSections())
} }
} }
fun search(query: String?) { fun search(query: String?, filters: List<Boolean>) {
viewModelScope.launch(IO) { viewModelScope.launch(IO) {
val result = repository.search(query) val result = repository.search(query, filters)
withContext(Main) { searchResults.postValue(result) } searchResults.postValue(result)
} }
} }
@ -190,6 +188,10 @@ class LibraryViewModel(
println("onShuffleModeChanged") println("onShuffleModeChanged")
} }
override fun onFavoriteStateChanged() {
println("onFavoriteStateChanged")
}
fun shuffleSongs() = viewModelScope.launch(IO) { fun shuffleSongs() = viewModelScope.launch(IO) {
val songs = repository.allSongs() val songs = repository.allSongs()
MusicPlayerRemote.openAndShuffleQueue( MusicPlayerRemote.openAndShuffleQueue(

View file

@ -26,23 +26,26 @@ import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.animation.DecelerateInterpolator import android.view.animation.DecelerateInterpolator
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentMiniPlayerBinding
import code.name.monkey.retromusic.extensions.accentColor import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.applyColor
import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.extensions.textColorPrimary import code.name.monkey.retromusic.extensions.textColorPrimary
import code.name.monkey.retromusic.extensions.textColorSecondary import code.name.monkey.retromusic.extensions.textColorSecondary
import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil
import kotlin.math.abs import kotlin.math.abs
import kotlinx.android.synthetic.main.fragment_mini_player.*
open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_player), open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_player),
MusicProgressViewUpdateHelper.Callback, View.OnClickListener { MusicProgressViewUpdateHelper.Callback, View.OnClickListener {
private var _binding: FragmentMiniPlayerBinding? = null
private val binding get() = _binding!!
private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -59,38 +62,38 @@ open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_p
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
_binding = FragmentMiniPlayerBinding.bind(view)
view.setOnTouchListener(FlingPlayBackController(requireContext())) view.setOnTouchListener(FlingPlayBackController(requireContext()))
setUpMiniPlayer() setUpMiniPlayer()
if (RetroUtil.isTablet()) { if (RetroUtil.isTablet()) {
actionNext.show() binding.actionNext.show()
actionPrevious.show() binding.actionPrevious.show()
actionNext?.show()
actionPrevious?.show()
} else { } else {
actionNext.visibility = if (PreferenceUtil.isExtraControls) View.VISIBLE else View.GONE binding.actionNext.visibility =
actionPrevious.visibility = if (PreferenceUtil.isExtraControls) View.VISIBLE else View.GONE
binding.actionPrevious.visibility =
if (PreferenceUtil.isExtraControls) View.VISIBLE else View.GONE if (PreferenceUtil.isExtraControls) View.VISIBLE else View.GONE
} }
actionNext.setOnClickListener(this) binding.actionNext.setOnClickListener(this)
actionPrevious.setOnClickListener(this) binding.actionPrevious.setOnClickListener(this)
actionNext?.setOnClickListener(this)
actionPrevious?.setOnClickListener(this)
} }
private fun setUpMiniPlayer() { private fun setUpMiniPlayer() {
setUpPlayPauseButton() setUpPlayPauseButton()
progressBar.accentColor() binding.progressBar.accentColor()
} }
private fun setUpPlayPauseButton() { private fun setUpPlayPauseButton() {
miniPlayerPlayPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) binding.miniPlayerPlayPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler())
} }
private fun updateSongTitle() { private fun updateSongTitle() {
val builder = SpannableStringBuilder()
val song = MusicPlayerRemote.currentSong val song = MusicPlayerRemote.currentSong
val builder = SpannableStringBuilder()
val title = SpannableString(song.title) val title = SpannableString(song.title)
title.setSpan(ForegroundColorSpan(textColorPrimary()), 0, title.length, 0) title.setSpan(ForegroundColorSpan(textColorPrimary()), 0, title.length, 0)
@ -99,17 +102,34 @@ open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_p
builder.append(title).append("").append(text) builder.append(title).append("").append(text)
miniPlayerTitle.isSelected = true binding.miniPlayerTitle.isSelected = true
miniPlayerTitle.text = builder binding.miniPlayerTitle.text = builder
// binding.title.isSelected = true
// binding.title.text = song.title
// binding.text.isSelected = true
// binding.text.text = song.artistName
}
private fun updateSongCover() {
// val song = MusicPlayerRemote.currentSong
// GlideApp.with(requireContext())
// .asBitmap()
// .songCoverOptions(song)
// .transition(RetroGlideExtension.getDefaultTransition())
// .load(RetroGlideExtension.getSongModel(song))
// .into(binding.image)
} }
override fun onServiceConnected() { override fun onServiceConnected() {
updateSongTitle() updateSongTitle()
updateSongCover()
updatePlayPauseDrawableState() updatePlayPauseDrawableState()
} }
override fun onPlayingMetaChanged() { override fun onPlayingMetaChanged() {
updateSongTitle() updateSongTitle()
updateSongCover()
} }
override fun onPlayStateChanged() { override fun onPlayStateChanged() {
@ -117,8 +137,8 @@ open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_p
} }
override fun onUpdateProgressViews(progress: Int, total: Int) { override fun onUpdateProgressViews(progress: Int, total: Int) {
progressBar.max = total binding.progressBar.max = total
val animator = ObjectAnimator.ofInt(progressBar, "progress", progress) val animator = ObjectAnimator.ofInt(binding.progressBar, "progress", progress)
animator.duration = 1000 animator.duration = 1000
animator.interpolator = DecelerateInterpolator() animator.interpolator = DecelerateInterpolator()
animator.start() animator.start()
@ -136,16 +156,12 @@ open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_p
protected fun updatePlayPauseDrawableState() { protected fun updatePlayPauseDrawableState() {
if (MusicPlayerRemote.isPlaying) { if (MusicPlayerRemote.isPlaying) {
miniPlayerPlayPauseButton.setImageResource(R.drawable.ic_pause) binding.miniPlayerPlayPauseButton.setImageResource(R.drawable.ic_pause)
} else { } else {
miniPlayerPlayPauseButton.setImageResource(R.drawable.ic_play_arrow) binding.miniPlayerPlayPauseButton.setImageResource(R.drawable.ic_play_arrow)
} }
} }
fun updateProgressBar(paletteColor: Int) {
progressBar.applyColor(paletteColor)
}
class FlingPlayBackController(context: Context) : View.OnTouchListener { class FlingPlayBackController(context: Context) : View.OnTouchListener {
private var flingPlayBackController: GestureDetector private var flingPlayBackController: GestureDetector
@ -178,4 +194,9 @@ open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_p
return flingPlayBackController.onTouchEvent(event) return flingPlayBackController.onTouchEvent(event)
} }
} }
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
} }

View file

@ -26,34 +26,38 @@ import android.widget.SeekBar
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentVolumeBinding
import code.name.monkey.retromusic.extensions.applyColor import code.name.monkey.retromusic.extensions.applyColor
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.volume.AudioVolumeObserver import code.name.monkey.retromusic.volume.AudioVolumeObserver
import code.name.monkey.retromusic.volume.OnAudioVolumeChangedListener import code.name.monkey.retromusic.volume.OnAudioVolumeChangedListener
import kotlinx.android.synthetic.main.fragment_volume.*
class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolumeChangedListener, class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolumeChangedListener,
View.OnClickListener { View.OnClickListener {
private var _binding: FragmentVolumeBinding? = null
private val binding get() = _binding!!
private var audioVolumeObserver: AudioVolumeObserver? = null private var audioVolumeObserver: AudioVolumeObserver? = null
private val audioManager: AudioManager? private val audioManager: AudioManager
get() = requireContext().getSystemService(Context.AUDIO_SERVICE) as AudioManager get() = requireContext().getSystemService(Context.AUDIO_SERVICE) as AudioManager
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View {
return inflater.inflate(R.layout.fragment_volume, container, false) _binding = FragmentVolumeBinding.inflate(inflater, container, false)
return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setTintable(ThemeStore.accentColor(requireContext())) setTintable(ThemeStore.accentColor(requireContext()))
volumeDown.setOnClickListener(this) binding.volumeDown.setOnClickListener(this)
volumeUp.setOnClickListener(this) binding.volumeUp.setOnClickListener(this)
} }
override fun onResume() { override fun onResume() {
@ -64,33 +68,30 @@ class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolum
audioVolumeObserver?.register(AudioManager.STREAM_MUSIC, this) audioVolumeObserver?.register(AudioManager.STREAM_MUSIC, this)
val audioManager = audioManager val audioManager = audioManager
if (audioManager != null) { binding.volumeSeekBar.max = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
volumeSeekBar.max = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) binding.volumeSeekBar.progress = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
volumeSeekBar.progress = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) binding.volumeSeekBar.setOnSeekBarChangeListener(this)
}
volumeSeekBar.setOnSeekBarChangeListener(this)
} }
override fun onAudioVolumeChanged(currentVolume: Int, maxVolume: Int) { override fun onAudioVolumeChanged(currentVolume: Int, maxVolume: Int) {
if (volumeSeekBar == null) { if (_binding != null) {
return binding.volumeSeekBar.max = maxVolume
binding.volumeSeekBar.progress = currentVolume
binding.volumeDown.setImageResource(if (currentVolume == 0) R.drawable.ic_volume_off else R.drawable.ic_volume_down)
} }
volumeSeekBar.max = maxVolume
volumeSeekBar.progress = currentVolume
volumeDown.setImageResource(if (currentVolume == 0) R.drawable.ic_volume_off else R.drawable.ic_volume_down)
} }
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
audioVolumeObserver?.unregister() audioVolumeObserver?.unregister()
_binding = null
} }
override fun onProgressChanged(seekBar: SeekBar, i: Int, b: Boolean) { override fun onProgressChanged(seekBar: SeekBar, i: Int, b: Boolean) {
val audioManager = audioManager val audioManager = audioManager
audioManager?.setStreamVolume(AudioManager.STREAM_MUSIC, i, 0) audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, i, 0)
setPauseWhenZeroVolume(i < 1) setPauseWhenZeroVolume(i < 1)
volumeDown?.setImageResource(if (i == 0) R.drawable.ic_volume_off else R.drawable.ic_volume_down) binding.volumeDown.setImageResource(if (i == 0) R.drawable.ic_volume_off else R.drawable.ic_volume_down)
} }
override fun onStartTrackingTouch(seekBar: SeekBar) { override fun onStartTrackingTouch(seekBar: SeekBar) {
@ -102,10 +103,10 @@ class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolum
override fun onClick(view: View) { override fun onClick(view: View) {
val audioManager = audioManager val audioManager = audioManager
when (view.id) { when (view.id) {
R.id.volumeDown -> audioManager?.adjustStreamVolume( R.id.volumeDown -> audioManager.adjustStreamVolume(
AudioManager.STREAM_MUSIC, AudioManager.ADJUST_LOWER, 0 AudioManager.STREAM_MUSIC, AudioManager.ADJUST_LOWER, 0
) )
R.id.volumeUp -> audioManager?.adjustStreamVolume( R.id.volumeUp -> audioManager.adjustStreamVolume(
AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, 0 AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, 0
) )
} }
@ -113,13 +114,13 @@ class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolum
fun tintWhiteColor() { fun tintWhiteColor() {
val color = Color.WHITE val color = Color.WHITE
volumeDown.setColorFilter(color, PorterDuff.Mode.SRC_IN) binding.volumeDown.setColorFilter(color, PorterDuff.Mode.SRC_IN)
volumeUp.setColorFilter(color, PorterDuff.Mode.SRC_IN) binding.volumeUp.setColorFilter(color, PorterDuff.Mode.SRC_IN)
volumeSeekBar.applyColor(color) binding.volumeSeekBar.applyColor(color)
} }
fun setTintable(color: Int) { fun setTintable(color: Int) {
volumeSeekBar.applyColor(color) binding.volumeSeekBar.applyColor(color)
} }
private fun setPauseWhenZeroVolume(pauseWhenZeroVolume: Boolean) { private fun setPauseWhenZeroVolume(pauseWhenZeroVolume: Boolean) {
@ -129,10 +130,10 @@ class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolum
} }
fun setTintableColor(color: Int) { fun setTintableColor(color: Int) {
volumeDown.setColorFilter(color, PorterDuff.Mode.SRC_IN) binding.volumeDown.setColorFilter(color, PorterDuff.Mode.SRC_IN)
volumeUp.setColorFilter(color, PorterDuff.Mode.SRC_IN) binding.volumeUp.setColorFilter(color, PorterDuff.Mode.SRC_IN)
// TintHelper.setTint(volumeSeekBar, color, false) // TintHelper.setTint(volumeSeekBar, color, false)
volumeSeekBar.applyColor(color) binding.volumeSeekBar.applyColor(color)
} }
companion object { companion object {

View file

@ -27,20 +27,20 @@ import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.Constants import code.name.monkey.retromusic.Constants
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.ContributorAdapter import code.name.monkey.retromusic.adapter.ContributorAdapter
import code.name.monkey.retromusic.databinding.FragmentAboutBinding
import code.name.monkey.retromusic.fragments.LibraryViewModel import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.util.NavigationUtil import code.name.monkey.retromusic.util.NavigationUtil
import kotlinx.android.synthetic.main.card_credit.*
import kotlinx.android.synthetic.main.card_other.*
import kotlinx.android.synthetic.main.card_retro_info.*
import kotlinx.android.synthetic.main.card_social.*
import org.koin.androidx.viewmodel.ext.android.sharedViewModel import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener { class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener {
private var _binding: FragmentAboutBinding? = null
private val binding get() = _binding!!
private val libraryViewModel by sharedViewModel<LibraryViewModel>() private val libraryViewModel by sharedViewModel<LibraryViewModel>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
version.setSummary(getAppVersion()) _binding = FragmentAboutBinding.bind(view)
binding.aboutContent.cardOther.version.setSummary(getAppVersion())
setUpView() setUpView()
loadContributors() loadContributors()
} }
@ -53,19 +53,20 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener {
} }
private fun setUpView() { private fun setUpView() {
appGithub.setOnClickListener(this) binding.aboutContent.cardRetroInfo.appGithub.setOnClickListener(this)
faqLink.setOnClickListener(this) binding.aboutContent.cardRetroInfo.faqLink.setOnClickListener(this)
telegramLink.setOnClickListener(this) binding.aboutContent.cardSocial.telegramLink.setOnClickListener(this)
appRate.setOnClickListener(this) binding.aboutContent.cardRetroInfo.appRate.setOnClickListener(this)
appTranslation.setOnClickListener(this) binding.aboutContent.cardRetroInfo.appTranslation.setOnClickListener(this)
appShare.setOnClickListener(this) binding.aboutContent.cardRetroInfo.appShare.setOnClickListener(this)
donateLink.setOnClickListener(this) binding.aboutContent.cardRetroInfo.donateLink.setOnClickListener(this)
instagramLink.setOnClickListener(this) binding.aboutContent.cardSocial.instagramLink.setOnClickListener(this)
twitterLink.setOnClickListener(this) binding.aboutContent.cardSocial.twitterLink.setOnClickListener(this)
changelog.setOnClickListener(this) binding.aboutContent.cardOther.changelog.setOnClickListener(this)
openSource.setOnClickListener(this) binding.aboutContent.cardOther.openSource.setOnClickListener(this)
pinterestLink.setOnClickListener(this) binding.aboutContent.cardSocial.pinterestLink.setOnClickListener(this)
bugReportLink.setOnClickListener(this) binding.aboutContent.cardRetroInfo.bugReportLink.setOnClickListener(this)
binding.aboutContent.cardSocial.websiteLink.setOnClickListener(this)
} }
override fun onClick(view: View) { override fun onClick(view: View) {
@ -80,9 +81,10 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener {
R.id.donateLink -> NavigationUtil.goToSupportDevelopment(requireActivity()) R.id.donateLink -> NavigationUtil.goToSupportDevelopment(requireActivity())
R.id.instagramLink -> openUrl(Constants.APP_INSTAGRAM_LINK) R.id.instagramLink -> openUrl(Constants.APP_INSTAGRAM_LINK)
R.id.twitterLink -> openUrl(Constants.APP_TWITTER_LINK) R.id.twitterLink -> openUrl(Constants.APP_TWITTER_LINK)
R.id.changelog -> openUrl(Constants.TELEGRAM_CHANGE_LOG) R.id.changelog -> NavigationUtil.gotoWhatNews(requireActivity())
R.id.openSource -> NavigationUtil.goToOpenSource(requireActivity()) R.id.openSource -> NavigationUtil.goToOpenSource(requireActivity())
R.id.bugReportLink -> NavigationUtil.bugReport(requireActivity()) R.id.bugReportLink -> NavigationUtil.bugReport(requireActivity())
R.id.websiteLink -> openUrl(Constants.WEBSITE)
} }
} }
@ -99,7 +101,7 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener {
} }
private fun shareApp() { private fun shareApp() {
ShareCompat.IntentBuilder.from(requireActivity()).setType("text/plain") ShareCompat.IntentBuilder(requireActivity()).setType("text/plain")
.setChooserTitle(R.string.share_app) .setChooserTitle(R.string.share_app)
.setText(String.format(getString(R.string.app_share), requireActivity().packageName)) .setText(String.format(getString(R.string.app_share), requireActivity().packageName))
.startChooser() .startChooser()
@ -107,7 +109,7 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener {
private fun loadContributors() { private fun loadContributors() {
val contributorAdapter = ContributorAdapter(emptyList()) val contributorAdapter = ContributorAdapter(emptyList())
recyclerView.apply { binding.aboutContent.cardCredit.recyclerView.apply {
layoutManager = LinearLayoutManager(requireContext()) layoutManager = LinearLayoutManager(requireContext())
itemAnimator = DefaultItemAnimator() itemAnimator = DefaultItemAnimator()
adapter = contributorAdapter adapter = contributorAdapter
@ -116,4 +118,9 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener {
contributorAdapter.swapData(contributors) contributorAdapter.swapData(contributors)
}) })
} }
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
} }

View file

@ -19,11 +19,12 @@ import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.view.* import android.view.*
import androidx.activity.addCallback
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.lifecycle.Observer import androidx.core.view.doOnPreDraw
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
@ -35,17 +36,19 @@ import code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackg
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.EXTRA_ALBUM_ID import code.name.monkey.retromusic.EXTRA_ALBUM_ID
import code.name.monkey.retromusic.EXTRA_ARTIST_ID import code.name.monkey.retromusic.EXTRA_ARTIST_ID
import code.name.monkey.retromusic.EXTRA_ARTIST_NAME
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.tageditor.AbsTagEditorActivity import code.name.monkey.retromusic.activities.tageditor.AbsTagEditorActivity
import code.name.monkey.retromusic.activities.tageditor.AlbumTagEditorActivity import code.name.monkey.retromusic.activities.tageditor.AlbumTagEditorActivity
import code.name.monkey.retromusic.adapter.album.HorizontalAlbumAdapter import code.name.monkey.retromusic.adapter.album.HorizontalAlbumAdapter
import code.name.monkey.retromusic.adapter.song.SimpleSongAdapter import code.name.monkey.retromusic.adapter.song.SimpleSongAdapter
import code.name.monkey.retromusic.databinding.FragmentAlbumDetailsBinding
import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog
import code.name.monkey.retromusic.dialogs.DeleteSongsDialog import code.name.monkey.retromusic.dialogs.DeleteSongsDialog
import code.name.monkey.retromusic.extensions.* import code.name.monkey.retromusic.extensions.*
import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import code.name.monkey.retromusic.glide.AlbumGlideRequest import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.ArtistGlideRequest import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.glide.SingleColorTarget import code.name.monkey.retromusic.glide.SingleColorTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
@ -54,6 +57,7 @@ import code.name.monkey.retromusic.helper.SortOrder.AlbumSongSortOrder.Companion
import code.name.monkey.retromusic.helper.SortOrder.AlbumSongSortOrder.Companion.SONG_TRACK_LIST import code.name.monkey.retromusic.helper.SortOrder.AlbumSongSortOrder.Companion.SONG_TRACK_LIST
import code.name.monkey.retromusic.helper.SortOrder.AlbumSongSortOrder.Companion.SONG_Z_A import code.name.monkey.retromusic.helper.SortOrder.AlbumSongSortOrder.Companion.SONG_Z_A
import code.name.monkey.retromusic.interfaces.IAlbumClickListener import code.name.monkey.retromusic.interfaces.IAlbumClickListener
import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.network.Result import code.name.monkey.retromusic.network.Result
@ -61,14 +65,12 @@ import code.name.monkey.retromusic.network.model.LastFmAlbum
import code.name.monkey.retromusic.repository.RealRepository import code.name.monkey.retromusic.repository.RealRepository
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroColorUtil
import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide import com.afollestad.materialcab.MaterialCab
import com.google.android.material.transition.MaterialArcMotion import com.google.android.material.transition.MaterialArcMotion
import com.google.android.material.transition.MaterialContainerTransform import com.google.android.material.transition.MaterialContainerTransform
import com.google.android.material.transition.MaterialElevationScale
import kotlinx.android.synthetic.main.fragment_album_content.*
import kotlinx.android.synthetic.main.fragment_album_details.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -78,7 +80,10 @@ import org.koin.core.parameter.parametersOf
import java.util.* import java.util.*
class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_details), class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_details),
IAlbumClickListener { IAlbumClickListener, ICabHolder {
private var _binding: FragmentAlbumDetailsBinding? = null
private val binding get() = _binding!!
private val arguments by navArgs<AlbumDetailsFragmentArgs>() private val arguments by navArgs<AlbumDetailsFragmentArgs>()
private val detailsViewModel by viewModel<AlbumDetailsViewModel> { private val detailsViewModel by viewModel<AlbumDetailsViewModel> {
@ -87,6 +92,7 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
private lateinit var simpleSongAdapter: SimpleSongAdapter private lateinit var simpleSongAdapter: SimpleSongAdapter
private lateinit var album: Album private lateinit var album: Album
private var albumArtistExists = false
private val savedSortOrder: String private val savedSortOrder: String
get() = PreferenceUtil.albumDetailSongSortOrder get() = PreferenceUtil.albumDetailSongSortOrder
@ -104,51 +110,70 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
_binding = FragmentAlbumDetailsBinding.bind(view)
setHasOptionsMenu(true) setHasOptionsMenu(true)
mainActivity.setBottomBarVisibility(false)
mainActivity.addMusicServiceEventListener(detailsViewModel) mainActivity.addMusicServiceEventListener(detailsViewModel)
mainActivity.setSupportActionBar(toolbar) mainActivity.setSupportActionBar(binding.toolbar)
toolbar.title = " " binding.toolbar.title = " "
ViewCompat.setTransitionName(albumCoverContainer, "album") ViewCompat.setTransitionName(binding.albumCoverContainer, arguments.extraAlbumId.toString())
postponeEnterTransition() postponeEnterTransition()
detailsViewModel.getAlbum().observe(viewLifecycleOwner, Observer { detailsViewModel.getAlbum().observe(viewLifecycleOwner, {
requireView().doOnPreDraw {
startPostponedEnterTransition() startPostponedEnterTransition()
}
albumArtistExists = !it.albumArtist.isNullOrEmpty()
showAlbum(it) showAlbum(it)
if (albumArtistExists) {
ViewCompat.setTransitionName(binding.artistImage, album.albumArtist)
} else {
ViewCompat.setTransitionName(binding.artistImage, album.artistId.toString())
}
}) })
setupRecyclerView() setupRecyclerView()
artistImage.setOnClickListener { artistView -> binding.artistImage.setOnClickListener { artistView ->
ViewCompat.setTransitionName(artistView, "artist") if (albumArtistExists) {
exitTransition = MaterialElevationScale(false).apply { findActivityNavController(R.id.fragment_container)
duration = 300L .navigate(
} R.id.albumArtistDetailsFragment,
reenterTransition = MaterialElevationScale(true).apply { bundleOf(EXTRA_ARTIST_NAME to album.albumArtist),
duration = 300L null,
} FragmentNavigatorExtras(artistView to album.albumArtist.toString())
)
} else {
findActivityNavController(R.id.fragment_container) findActivityNavController(R.id.fragment_container)
.navigate( .navigate(
R.id.artistDetailsFragment, R.id.artistDetailsFragment,
bundleOf(EXTRA_ARTIST_ID to album.artistId), bundleOf(EXTRA_ARTIST_ID to album.artistId),
null, null,
FragmentNavigatorExtras(artistView to "artist") FragmentNavigatorExtras(artistView to album.artistId.toString())
) )
} }
playAction.setOnClickListener {
}
binding.fragmentAlbumContent.playAction.setOnClickListener {
MusicPlayerRemote.openQueue(album.songs, 0, true) MusicPlayerRemote.openQueue(album.songs, 0, true)
} }
shuffleAction.setOnClickListener { binding.fragmentAlbumContent.shuffleAction.setOnClickListener {
MusicPlayerRemote.openAndShuffleQueue( MusicPlayerRemote.openAndShuffleQueue(
album.songs, album.songs,
true true
) )
} }
aboutAlbumText.setOnClickListener { binding.fragmentAlbumContent.aboutAlbumText.setOnClickListener {
if (aboutAlbumText.maxLines == 4) { if (binding.fragmentAlbumContent.aboutAlbumText.maxLines == 4) {
aboutAlbumText.maxLines = Integer.MAX_VALUE binding.fragmentAlbumContent.aboutAlbumText.maxLines = Integer.MAX_VALUE
} else { } else {
aboutAlbumText.maxLines = 4 binding.fragmentAlbumContent.aboutAlbumText.maxLines = 4
}
}
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
if (!handleBackPress()) {
remove()
requireActivity().onBackPressed()
} }
} }
} }
@ -163,9 +188,9 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
requireActivity() as AppCompatActivity, requireActivity() as AppCompatActivity,
ArrayList(), ArrayList(),
R.layout.item_song, R.layout.item_song,
null this
) )
recyclerView.apply { binding.fragmentAlbumContent.recyclerView.apply {
layoutManager = LinearLayoutManager(requireContext()) layoutManager = LinearLayoutManager(requireContext())
itemAnimator = DefaultItemAnimator() itemAnimator = DefaultItemAnimator()
isNestedScrollingEnabled = false isNestedScrollingEnabled = false
@ -179,21 +204,21 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
} }
this.album = album this.album = album
albumTitle.text = album.title binding.albumTitle.text = album.title
val songText = resources.getQuantityString( val songText = resources.getQuantityString(
R.plurals.albumSongs, R.plurals.albumSongs,
album.songCount, album.songCount,
album.songCount album.songCount
) )
songTitle.text = songText binding.fragmentAlbumContent.songTitle.text = songText
if (MusicUtil.getYearString(album.year) == "-") { if (MusicUtil.getYearString(album.year) == "-") {
albumText.text = String.format( binding.albumText.text = String.format(
"%s • %s", "%s • %s",
album.artistName, if (albumArtistExists) album.albumArtist else album.artistName,
MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(album.songs)) MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(album.songs))
) )
} else { } else {
albumText.text = String.format( binding.albumText.text = String.format(
"%s • %s • %s", "%s • %s • %s",
album.artistName, album.artistName,
MusicUtil.getYearString(album.year), MusicUtil.getYearString(album.year),
@ -202,11 +227,18 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
} }
loadAlbumCover(album) loadAlbumCover(album)
simpleSongAdapter.swapDataSet(album.songs) simpleSongAdapter.swapDataSet(album.songs)
detailsViewModel.getArtist(album.artistId).observe(viewLifecycleOwner, Observer { if (albumArtistExists) {
detailsViewModel.getAlbumArtist(album.albumArtist.toString()).observe(viewLifecycleOwner, {
loadArtistImage(it) loadArtistImage(it)
}) })
} else {
detailsViewModel.getArtist(album.artistId).observe(viewLifecycleOwner, {
loadArtistImage(it)
})
}
detailsViewModel.getAlbumInfo(album).observe(viewLifecycleOwner, Observer { result ->
detailsViewModel.getAlbumInfo(album).observe(viewLifecycleOwner, { result ->
when (result) { when (result) {
is Result.Loading -> { is Result.Loading -> {
println("Loading") println("Loading")
@ -222,41 +254,44 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
} }
private fun moreAlbums(albums: List<Album>) { private fun moreAlbums(albums: List<Album>) {
moreTitle.show() binding.fragmentAlbumContent.moreTitle.show()
moreRecyclerView.show() binding.fragmentAlbumContent.moreRecyclerView.show()
moreTitle.text = String.format(getString(R.string.label_more_from), album.artistName) binding.fragmentAlbumContent.moreTitle.text =
String.format(getString(R.string.label_more_from), album.artistName)
val albumAdapter = val albumAdapter =
HorizontalAlbumAdapter(requireActivity() as AppCompatActivity, albums, null, this) HorizontalAlbumAdapter(requireActivity() as AppCompatActivity, albums, this, this)
moreRecyclerView.layoutManager = GridLayoutManager( binding.fragmentAlbumContent.moreRecyclerView.layoutManager = GridLayoutManager(
requireContext(), requireContext(),
1, 1,
GridLayoutManager.HORIZONTAL, GridLayoutManager.HORIZONTAL,
false false
) )
moreRecyclerView.adapter = albumAdapter binding.fragmentAlbumContent.moreRecyclerView.adapter = albumAdapter
} }
private fun aboutAlbum(lastFmAlbum: LastFmAlbum) { private fun aboutAlbum(lastFmAlbum: LastFmAlbum) {
if (lastFmAlbum.album != null) { if (lastFmAlbum.album != null) {
if (lastFmAlbum.album.wiki != null) { if (lastFmAlbum.album.wiki != null) {
aboutAlbumText.show() binding.fragmentAlbumContent.aboutAlbumText.show()
aboutAlbumTitle.show() binding.fragmentAlbumContent.aboutAlbumTitle.show()
aboutAlbumTitle.text = binding.fragmentAlbumContent.aboutAlbumTitle.text =
String.format(getString(R.string.about_album_label), lastFmAlbum.album.name) String.format(getString(R.string.about_album_label), lastFmAlbum.album.name)
aboutAlbumText.text = HtmlCompat.fromHtml( binding.fragmentAlbumContent.aboutAlbumText.text = HtmlCompat.fromHtml(
lastFmAlbum.album.wiki.content, lastFmAlbum.album.wiki.content,
HtmlCompat.FROM_HTML_MODE_LEGACY HtmlCompat.FROM_HTML_MODE_LEGACY
) )
} }
if (lastFmAlbum.album.listeners.isNotEmpty()) { if (lastFmAlbum.album.listeners.isNotEmpty()) {
listeners.show() binding.fragmentAlbumContent.listeners.show()
listenersLabel.show() binding.fragmentAlbumContent.listenersLabel.show()
scrobbles.show() binding.fragmentAlbumContent.scrobbles.show()
scrobblesLabel.show() binding.fragmentAlbumContent.scrobblesLabel.show()
listeners.text = RetroUtil.formatValue(lastFmAlbum.album.listeners.toFloat()) binding.fragmentAlbumContent.listeners.text =
scrobbles.text = RetroUtil.formatValue(lastFmAlbum.album.playcount.toFloat()) RetroUtil.formatValue(lastFmAlbum.album.listeners.toFloat())
binding.fragmentAlbumContent.scrobbles.text =
RetroUtil.formatValue(lastFmAlbum.album.playcount.toFloat())
} }
} }
} }
@ -265,24 +300,22 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
detailsViewModel.getMoreAlbums(artist).observe(viewLifecycleOwner, { detailsViewModel.getMoreAlbums(artist).observe(viewLifecycleOwner, {
moreAlbums(it) moreAlbums(it)
}) })
ArtistGlideRequest.Builder.from(Glide.with(requireContext()), artist) GlideApp.with(requireContext()).asBitmapPalette().artistImageOptions(artist)
.forceDownload(PreferenceUtil.isAllowedToDownloadMetadata()) //.forceDownload(PreferenceUtil.isAllowedToDownloadMetadata())
.generatePalette(requireContext()) .load(RetroGlideExtension.getArtistModel(artist, PreferenceUtil.isAllowedToDownloadMetadata()))
.build()
.dontAnimate() .dontAnimate()
.dontTransform() .dontTransform()
.into(object : RetroMusicColoredTarget(artistImage) { .into(object : RetroMusicColoredTarget(binding.artistImage) {
override fun onColorReady(colors: MediaNotificationProcessor) { override fun onColorReady(colors: MediaNotificationProcessor) {
} }
}) })
} }
private fun loadAlbumCover(album: Album) { private fun loadAlbumCover(album: Album) {
AlbumGlideRequest.Builder.from(Glide.with(requireContext()), album.safeGetFirstSong()) GlideApp.with(requireContext()).asBitmapPalette().albumCoverOptions(album.safeGetFirstSong())
.checkIgnoreMediaStore() //.checkIgnoreMediaStore()
.generatePalette(requireContext()) .load(RetroGlideExtension.getSongModel(album.safeGetFirstSong()))
.build() .into(object : SingleColorTarget(binding.image) {
.into(object : SingleColorTarget(image) {
override fun onColorReady(color: Int) { override fun onColorReady(color: Int) {
setColors(color) setColors(color)
} }
@ -290,23 +323,17 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
} }
private fun setColors(color: Int) { private fun setColors(color: Int) {
shuffleAction?.applyColor(color) binding.fragmentAlbumContent.shuffleAction.applyColor(color)
playAction?.applyOutlineColor(color) binding.fragmentAlbumContent.playAction.applyOutlineColor(color)
} }
override fun onAlbumClick(albumId: Long, view: View) { override fun onAlbumClick(albumId: Long, view: View) {
exitTransition = MaterialElevationScale(false).apply {
duration = 300L
}
reenterTransition = MaterialElevationScale(false).apply {
duration = 300L
}
findNavController().navigate( findNavController().navigate(
R.id.albumDetailsFragment, R.id.albumDetailsFragment,
bundleOf(EXTRA_ALBUM_ID to albumId), bundleOf(EXTRA_ALBUM_ID to albumId),
null, null,
FragmentNavigatorExtras( FragmentNavigatorExtras(
view to "album" view to albumId.toString()
) )
) )
} }
@ -318,9 +345,9 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
setUpSortOrderMenu(sortOrder.subMenu) setUpSortOrderMenu(sortOrder.subMenu)
ToolbarContentTintHelper.handleOnCreateOptionsMenu( ToolbarContentTintHelper.handleOnCreateOptionsMenu(
requireContext(), requireContext(),
toolbar, binding.toolbar,
menu, menu,
getToolbarBackgroundColor(toolbar) getToolbarBackgroundColor(binding.toolbar)
) )
} }
@ -360,7 +387,7 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
intent.putExtra(AbsTagEditorActivity.EXTRA_ID, album.id) intent.putExtra(AbsTagEditorActivity.EXTRA_ID, album.id)
val options = ActivityOptions.makeSceneTransitionAnimation( val options = ActivityOptions.makeSceneTransitionAnimation(
requireActivity(), requireActivity(),
albumCoverContainer, binding.albumCoverContainer,
"${getString(R.string.transition_album_art)}_${album.id}" "${getString(R.string.transition_album_art)}_${album.id}"
) )
startActivityForResult( startActivityForResult(
@ -421,6 +448,37 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
simpleSongAdapter.swapDataSet(album.songs) simpleSongAdapter.swapDataSet(album.songs)
} }
private fun handleBackPress(): Boolean {
cab?.let {
if (it.isActive) {
it.finish()
return true
}
}
return false
}
private var cab: MaterialCab? = null
override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab {
cab?.let {
if (it.isActive) {
it.finish()
}
}
cab = MaterialCab(mainActivity, R.id.cab_stub)
.setMenu(menuRes)
.setCloseDrawableRes(R.drawable.ic_close)
.setBackgroundColor(RetroColorUtil.shiftBackgroundColorForLightText(surfaceColor()))
.start(callback)
return cab as MaterialCab
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
companion object { companion object {
const val TAG_EDITOR_REQUEST = 9002 const val TAG_EDITOR_REQUEST = 9002
} }

View file

@ -14,11 +14,7 @@
*/ */
package code.name.monkey.retromusic.fragments.albums package code.name.monkey.retromusic.fragments.albums
import androidx.lifecycle.LiveData import androidx.lifecycle.*
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.liveData
import androidx.lifecycle.viewModelScope
import code.name.monkey.retromusic.interfaces.IMusicServiceEventListener import code.name.monkey.retromusic.interfaces.IMusicServiceEventListener
import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.model.Artist
@ -51,6 +47,11 @@ class AlbumDetailsViewModel(
emit(artist) emit(artist)
} }
fun getAlbumArtist(artistName: String): LiveData<Artist> = liveData(IO) {
val artist = repository.albumArtistByName(artistName)
emit(artist)
}
fun getAlbumInfo(album: Album): LiveData<Result<LastFmAlbum>> = liveData { fun getAlbumInfo(album: Album): LiveData<Result<LastFmAlbum>> = liveData {
emit(Result.Loading) emit(Result.Loading)
emit(repository.albumInfo(album.artistName ?: "-", album.title ?: "-")) emit(repository.albumInfo(album.artistName ?: "-", album.title ?: "-"))
@ -73,4 +74,5 @@ class AlbumDetailsViewModel(
override fun onPlayStateChanged() {} override fun onPlayStateChanged() {}
override fun onRepeatModeChanged() {} override fun onRepeatModeChanged() {}
override fun onShuffleModeChanged() {} override fun onShuffleModeChanged() {}
override fun onFavoriteStateChanged() {}
} }

View file

@ -16,6 +16,7 @@ package code.name.monkey.retromusic.fragments.albums
import android.os.Bundle import android.os.Bundle
import android.view.* import android.view.*
import androidx.activity.addCallback
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
@ -33,7 +34,7 @@ import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroColorUtil import code.name.monkey.retromusic.util.RetroColorUtil
import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil
import com.afollestad.materialcab.MaterialCab import com.afollestad.materialcab.MaterialCab
import com.google.android.material.transition.MaterialElevationScale import com.google.android.gms.cast.framework.CastButtonFactory
class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridLayoutManager>(), class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridLayoutManager>(),
IAlbumClickListener, ICabHolder { IAlbumClickListener, ICabHolder {
@ -46,7 +47,16 @@ class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridL
else else
adapter?.swapDataSet(listOf()) adapter?.swapDataSet(listOf())
}) })
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
if (!handleBackPress()) {
remove()
requireActivity().onBackPressed()
} }
}
}
override val titleRes: Int
get() = R.string.albums
override val emptyMessage: Int override val emptyMessage: Int
get() = R.string.no_albums get() = R.string.no_albums
@ -114,20 +124,15 @@ class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridL
} }
override fun onAlbumClick(albumId: Long, view: View) { override fun onAlbumClick(albumId: Long, view: View) {
exitTransition = MaterialElevationScale(false).apply {
duration = 300L
}
reenterTransition = MaterialElevationScale(true).apply {
duration = 300L
}
findNavController().navigate( findNavController().navigate(
R.id.albumDetailsFragment, R.id.albumDetailsFragment,
bundleOf(EXTRA_ALBUM_ID to albumId), bundleOf(EXTRA_ALBUM_ID to albumId),
null, null,
FragmentNavigatorExtras( FragmentNavigatorExtras(
view to "album" view to albumId.toString()
) )
) )
reenterTransition = null
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
@ -140,6 +145,8 @@ class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridL
val layoutItem = menu.findItem(R.id.action_layout_type) val layoutItem = menu.findItem(R.id.action_layout_type)
setupLayoutMenu(layoutItem.subMenu) setupLayoutMenu(layoutItem.subMenu)
setUpSortOrderMenu(menu.findItem(R.id.action_sort_order).subMenu) setUpSortOrderMenu(menu.findItem(R.id.action_sort_order).subMenu)
//Setting up cast button
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.action_cast)
} }
private fun setUpSortOrderMenu( private fun setUpSortOrderMenu(
@ -175,6 +182,13 @@ class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridL
R.string.sort_order_year R.string.sort_order_year
).isChecked = ).isChecked =
currentSortOrder.equals(AlbumSortOrder.ALBUM_YEAR) currentSortOrder.equals(AlbumSortOrder.ALBUM_YEAR)
sortOrderMenu.add(
0,
R.id.action_album_sort_order_num_songs,
4,
R.string.sort_order_num_songs
).isChecked =
currentSortOrder.equals(AlbumSortOrder.ALBUM_NUMBER_OF_SONGS)
sortOrderMenu.setGroupCheckable(0, true, true) sortOrderMenu.setGroupCheckable(0, true, true)
} }
@ -251,6 +265,7 @@ class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridL
R.id.action_album_sort_order_desc -> AlbumSortOrder.ALBUM_Z_A R.id.action_album_sort_order_desc -> AlbumSortOrder.ALBUM_Z_A
R.id.action_album_sort_order_artist -> AlbumSortOrder.ALBUM_ARTIST R.id.action_album_sort_order_artist -> AlbumSortOrder.ALBUM_ARTIST
R.id.action_album_sort_order_year -> AlbumSortOrder.ALBUM_YEAR R.id.action_album_sort_order_year -> AlbumSortOrder.ALBUM_YEAR
R.id.action_album_sort_order_num_songs -> AlbumSortOrder.ALBUM_NUMBER_OF_SONGS
else -> PreferenceUtil.albumSortOrder else -> PreferenceUtil.albumSortOrder
} }
if (sortOrder != PreferenceUtil.albumSortOrder) { if (sortOrder != PreferenceUtil.albumSortOrder) {
@ -303,6 +318,21 @@ class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridL
return false return false
} }
override fun onResume() {
super.onResume()
libraryViewModel.forceReload(ReloadType.Albums)
}
private fun handleBackPress(): Boolean {
cab?.let {
if (it.isActive) {
it.finish()
return true
}
}
return false
}
private var cab: MaterialCab? = null private var cab: MaterialCab? = null
override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab { override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab {

View file

@ -0,0 +1,342 @@
package code.name.monkey.retromusic.fragments.artists
import android.app.Activity
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.text.Spanned
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.activity.addCallback
import androidx.core.os.bundleOf
import androidx.core.text.HtmlCompat
import androidx.core.view.ViewCompat
import androidx.core.view.doOnPreDraw
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import code.name.monkey.retromusic.EXTRA_ALBUM_ID
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.album.HorizontalAlbumAdapter
import code.name.monkey.retromusic.adapter.song.SimpleSongAdapter
import code.name.monkey.retromusic.databinding.FragmentArtistDetailsBinding
import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog
import code.name.monkey.retromusic.extensions.*
import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.SingleColorTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.IAlbumClickListener
import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.network.Result
import code.name.monkey.retromusic.network.model.LastFmArtist
import code.name.monkey.retromusic.repository.RealRepository
import code.name.monkey.retromusic.util.CustomArtistImageUtil
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.RetroColorUtil
import code.name.monkey.retromusic.util.RetroUtil
import com.afollestad.materialcab.MaterialCab
import com.google.android.material.transition.MaterialContainerTransform
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.android.ext.android.get
import java.util.*
import kotlin.collections.ArrayList
abstract class AbsArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_artist_details),
IAlbumClickListener, ICabHolder {
private var _binding: FragmentArtistDetailsBinding? = null
private val binding get() = _binding!!
abstract val detailsViewModel: ArtistDetailsViewModel
abstract val artistId: Long?
abstract val artistName: String?
private lateinit var artist: Artist
private lateinit var songAdapter: SimpleSongAdapter
private lateinit var albumAdapter: HorizontalAlbumAdapter
private var forceDownload: Boolean = false
private var lang: String? = null
private var biography: Spanned? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedElementEnterTransition = MaterialContainerTransform().apply {
drawingViewId = R.id.fragment_container
duration = 300L
scrimColor = Color.TRANSPARENT
setAllContainerColors(requireContext().resolveColor(R.attr.colorSurface))
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentArtistDetailsBinding.bind(view)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setHasOptionsMenu(true)
mainActivity.addMusicServiceEventListener(detailsViewModel)
mainActivity.setSupportActionBar(binding.toolbar)
binding.toolbar.title = null
ViewCompat.setTransitionName(
binding.artistCoverContainer,
(artistId ?: artistName).toString()
)
postponeEnterTransition()
detailsViewModel.getArtist().observe(viewLifecycleOwner, {
requireView().doOnPreDraw {
startPostponedEnterTransition()
}
showArtist(it)
})
setupRecyclerView()
binding.fragmentArtistContent.playAction.apply {
setOnClickListener { MusicPlayerRemote.openQueue(artist.songs, 0, true) }
}
binding.fragmentArtistContent.shuffleAction.apply {
setOnClickListener { MusicPlayerRemote.openAndShuffleQueue(artist.songs, true) }
}
binding.fragmentArtistContent.biographyText.setOnClickListener {
if (binding.fragmentArtistContent.biographyText.maxLines == 4) {
binding.fragmentArtistContent.biographyText.maxLines = Integer.MAX_VALUE
} else {
binding.fragmentArtistContent.biographyText.maxLines = 4
}
}
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
if (!handleBackPress()) {
remove()
requireActivity().onBackPressed()
}
}
}
private fun setupRecyclerView() {
albumAdapter = HorizontalAlbumAdapter(requireActivity(), ArrayList(), this, this)
binding.fragmentArtistContent.albumRecyclerView.apply {
itemAnimator = DefaultItemAnimator()
layoutManager = GridLayoutManager(this.context, 1, GridLayoutManager.HORIZONTAL, false)
adapter = albumAdapter
}
songAdapter = SimpleSongAdapter(requireActivity(), ArrayList(), R.layout.item_song, this)
binding.fragmentArtistContent.recyclerView.apply {
itemAnimator = DefaultItemAnimator()
layoutManager = LinearLayoutManager(this.context)
adapter = songAdapter
}
}
private fun showArtist(artist: Artist) {
this.artist = artist
loadArtistImage(artist)
if (RetroUtil.isAllowedToDownloadMetadata(requireContext())) {
loadBiography(artist.name)
}
binding.artistTitle.text = artist.name
binding.text.text = String.format(
"%s • %s",
MusicUtil.getArtistInfoString(requireContext(), artist),
MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(artist.songs))
)
val songText = resources.getQuantityString(
R.plurals.albumSongs,
artist.songCount,
artist.songCount
)
val albumText = resources.getQuantityString(
R.plurals.albums,
artist.songCount,
artist.songCount
)
binding.fragmentArtistContent.songTitle.text = songText
binding.fragmentArtistContent.albumTitle.text = albumText
songAdapter.swapDataSet(artist.songs.sortedBy { it.trackNumber })
albumAdapter.swapDataSet(artist.albums)
}
private fun loadBiography(
name: String,
lang: String? = Locale.getDefault().language
) {
biography = null
this.lang = lang
detailsViewModel.getArtistInfo(name, lang, null)
.observe(viewLifecycleOwner, { result ->
when (result) {
is Result.Loading -> println("Loading")
is Result.Error -> println("Error")
is Result.Success -> artistInfo(result.data)
}
})
}
private fun artistInfo(lastFmArtist: LastFmArtist?) {
if (lastFmArtist != null && lastFmArtist.artist != null && lastFmArtist.artist.bio != null) {
val bioContent = lastFmArtist.artist.bio.content
if (bioContent != null && bioContent.trim { it <= ' ' }.isNotEmpty()) {
binding.fragmentArtistContent.biographyText.visibility = View.VISIBLE
binding.fragmentArtistContent.biographyTitle.visibility = View.VISIBLE
biography = HtmlCompat.fromHtml(bioContent, HtmlCompat.FROM_HTML_MODE_LEGACY)
binding.fragmentArtistContent.biographyText.text = biography
if (lastFmArtist.artist.stats.listeners.isNotEmpty()) {
binding.fragmentArtistContent.listeners.show()
binding.fragmentArtistContent.listenersLabel.show()
binding.fragmentArtistContent.scrobbles.show()
binding.fragmentArtistContent.scrobblesLabel.show()
binding.fragmentArtistContent.listeners.text =
RetroUtil.formatValue(lastFmArtist.artist.stats.listeners.toFloat())
binding.fragmentArtistContent.scrobbles.text =
RetroUtil.formatValue(lastFmArtist.artist.stats.playcount.toFloat())
}
}
}
// If the "lang" parameter is set and no biography is given, retry with default language
if (biography == null && lang != null) {
loadBiography(artist.name, null)
}
}
private fun loadArtistImage(artist: Artist) {
GlideApp.with(requireContext()).asBitmapPalette().artistImageOptions(artist)
.load(RetroGlideExtension.getArtistModel(artist))
.dontAnimate()
.into(object : SingleColorTarget(binding.image) {
override fun onColorReady(color: Int) {
setColors(color)
}
})
}
private fun setColors(color: Int) {
if (_binding != null) {
binding.fragmentArtistContent.shuffleAction.applyColor(color)
binding.fragmentArtistContent.playAction.applyOutlineColor(color)
}
}
override fun onAlbumClick(albumId: Long, view: View) {
findNavController().navigate(
R.id.albumDetailsFragment,
bundleOf(EXTRA_ALBUM_ID to albumId),
null,
FragmentNavigatorExtras(
view to albumId.toString()
)
)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return handleSortOrderMenuItem(item)
}
private fun handleSortOrderMenuItem(item: MenuItem): Boolean {
val songs = artist.songs
when (item.itemId) {
android.R.id.home -> findNavController().navigateUp()
R.id.action_play_next -> {
MusicPlayerRemote.playNext(songs)
return true
}
R.id.action_add_to_current_playing -> {
MusicPlayerRemote.enqueue(songs)
return true
}
R.id.action_add_to_playlist -> {
lifecycleScope.launch(Dispatchers.IO) {
val playlists = get<RealRepository>().fetchPlaylists()
withContext(Dispatchers.Main) {
AddToPlaylistDialog.create(playlists, songs)
.show(childFragmentManager, "ADD_PLAYLIST")
}
}
return true
}
R.id.action_set_artist_image -> {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "image/*"
startActivityForResult(
Intent.createChooser(intent, getString(R.string.pick_from_local_storage)),
REQUEST_CODE_SELECT_IMAGE
)
return true
}
R.id.action_reset_artist_image -> {
showToast(resources.getString(R.string.updating))
CustomArtistImageUtil.getInstance(requireContext()).resetCustomArtistImage(artist)
forceDownload = true
return true
}
}
return true
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQUEST_CODE_SELECT_IMAGE -> if (resultCode == Activity.RESULT_OK) {
data?.data?.let {
CustomArtistImageUtil.getInstance(requireContext())
.setCustomArtistImage(artist, it)
}
}
else -> if (resultCode == Activity.RESULT_OK) {
println("OK")
}
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.menu_artist_detail, menu)
}
private fun handleBackPress(): Boolean {
cab?.let {
if (it.isActive) {
it.finish()
return true
}
}
return false
}
private var cab: MaterialCab? = null
override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab {
cab?.let {
if (it.isActive) {
it.finish()
}
}
cab = MaterialCab(mainActivity, R.id.cab_stub)
.setMenu(menuRes)
.setCloseDrawableRes(R.drawable.ic_close)
.setBackgroundColor(RetroColorUtil.shiftBackgroundColorForLightText(surfaceColor()))
.start(callback)
return cab as MaterialCab
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
companion object {
const val REQUEST_CODE_SELECT_IMAGE = 9002
}
}

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.fragments.artists
import androidx.navigation.fragment.navArgs
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
class AlbumArtistDetailsFragment : AbsArtistDetailsFragment() {
private val arguments by navArgs<AlbumArtistDetailsFragmentArgs>()
override val detailsViewModel: ArtistDetailsViewModel by viewModel {
parametersOf(null, arguments.extraArtistName)
}
override val artistId: Long?
get() = null
override val artistName: String
get() = arguments.extraArtistName
}

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.fragments.artists
import androidx.lifecycle.*
import code.name.monkey.retromusic.interfaces.IMusicServiceEventListener
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.network.Result
import code.name.monkey.retromusic.network.model.LastFmArtist
import code.name.monkey.retromusic.repository.RealRepository
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch
class AlbumArtistDetailsViewModel(
private val realRepository: RealRepository,
private val artistName: String
) : ViewModel(), IMusicServiceEventListener {
private val artistDetails = MutableLiveData<Artist>()
init {
fetchAlbumArtist()
}
private fun fetchAlbumArtist() {
viewModelScope.launch(IO) {
artistDetails.postValue(realRepository.albumArtistByName(artistName))
}
}
fun getArtist(): LiveData<Artist> = artistDetails
fun getArtistInfo(
name: String,
lang: String?,
cache: String?
): LiveData<Result<LastFmArtist>> = liveData(IO) {
emit(Result.Loading)
val info = realRepository.artistInfo(name, lang, cache)
emit(info)
}
override fun onMediaStoreChanged() {
fetchAlbumArtist()
}
override fun onServiceConnected() {}
override fun onServiceDisconnected() {}
override fun onQueueChanged() {}
override fun onFavoriteStateChanged() {}
override fun onPlayingMetaChanged() {}
override fun onPlayStateChanged() {}
override fun onRepeatModeChanged() {}
override fun onShuffleModeChanged() {}
}

View file

@ -14,298 +14,18 @@
*/ */
package code.name.monkey.retromusic.fragments.artists package code.name.monkey.retromusic.fragments.artists
import android.app.Activity
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.text.Spanned
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.core.os.bundleOf
import androidx.core.text.HtmlCompat
import androidx.core.view.ViewCompat
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.retromusic.EXTRA_ALBUM_ID
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.album.HorizontalAlbumAdapter
import code.name.monkey.retromusic.adapter.song.SimpleSongAdapter
import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog
import code.name.monkey.retromusic.extensions.*
import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import code.name.monkey.retromusic.glide.ArtistGlideRequest
import code.name.monkey.retromusic.glide.SingleColorTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.IAlbumClickListener
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.network.Result
import code.name.monkey.retromusic.network.model.LastFmArtist
import code.name.monkey.retromusic.repository.RealRepository
import code.name.monkey.retromusic.util.CustomArtistImageUtil
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.RetroUtil
import com.bumptech.glide.Glide
import com.google.android.material.transition.MaterialContainerTransform
import com.google.android.material.transition.MaterialElevationScale
import kotlinx.android.synthetic.main.fragment_artist_content.*
import kotlinx.android.synthetic.main.fragment_artist_details.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.android.ext.android.get
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
import java.util.*
import kotlin.collections.ArrayList
class ArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_artist_details), class ArtistDetailsFragment : AbsArtistDetailsFragment() {
IAlbumClickListener {
private val arguments by navArgs<ArtistDetailsFragmentArgs>() private val arguments by navArgs<ArtistDetailsFragmentArgs>()
private val detailsViewModel: ArtistDetailsViewModel by viewModel { override val detailsViewModel: ArtistDetailsViewModel by viewModel {
parametersOf(arguments.extraArtistId) parametersOf(arguments.extraArtistId, null)
} }
private lateinit var artist: Artist override val artistId: Long
private lateinit var songAdapter: SimpleSongAdapter get() = arguments.extraArtistId
private lateinit var albumAdapter: HorizontalAlbumAdapter override val artistName: String?
private var forceDownload: Boolean = false get() = null
private var lang: String? = null
private var biography: Spanned? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedElementEnterTransition = MaterialContainerTransform().apply {
drawingViewId = R.id.fragment_container
duration = 300L
scrimColor = Color.TRANSPARENT
setAllContainerColors(requireContext().resolveColor(R.attr.colorSurface))
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setHasOptionsMenu(true)
mainActivity.setBottomBarVisibility(false)
mainActivity.addMusicServiceEventListener(detailsViewModel)
mainActivity.setSupportActionBar(toolbar)
toolbar.title = null
ViewCompat.setTransitionName(artistCoverContainer, "artist")
postponeEnterTransition()
detailsViewModel.getArtist().observe(viewLifecycleOwner, {
startPostponedEnterTransition()
showArtist(it)
})
setupRecyclerView()
playAction.apply {
setOnClickListener { MusicPlayerRemote.openQueue(artist.songs, 0, true) }
}
shuffleAction.apply {
setOnClickListener { MusicPlayerRemote.openAndShuffleQueue(artist.songs, true) }
}
biographyText.setOnClickListener {
if (biographyText.maxLines == 4) {
biographyText.maxLines = Integer.MAX_VALUE
} else {
biographyText.maxLines = 4
}
}
}
private fun setupRecyclerView() {
albumAdapter = HorizontalAlbumAdapter(requireActivity(), ArrayList(), null, this)
albumRecyclerView.apply {
itemAnimator = DefaultItemAnimator()
layoutManager = GridLayoutManager(this.context, 1, GridLayoutManager.HORIZONTAL, false)
adapter = albumAdapter
}
songAdapter = SimpleSongAdapter(requireActivity(), ArrayList(), R.layout.item_song, null)
recyclerView.apply {
itemAnimator = DefaultItemAnimator()
layoutManager = LinearLayoutManager(this.context)
adapter = songAdapter
}
}
private fun showArtist(artist: Artist) {
this.artist = artist
loadArtistImage(artist)
if (RetroUtil.isAllowedToDownloadMetadata(requireContext())) {
loadBiography(artist.name)
}
artistTitle.text = artist.name
text.text = String.format(
"%s • %s",
MusicUtil.getArtistInfoString(requireContext(), artist),
MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(artist.songs))
)
val songText = resources.getQuantityString(
R.plurals.albumSongs,
artist.songCount,
artist.songCount
)
val albumText = resources.getQuantityString(
R.plurals.albums,
artist.songCount,
artist.songCount
)
songTitle.text = songText
albumTitle.text = albumText
songAdapter.swapDataSet(artist.songs.sortedBy { it.trackNumber })
albumAdapter.swapDataSet(artist.albums)
}
private fun loadBiography(
name: String,
lang: String? = Locale.getDefault().language
) {
biography = null
this.lang = lang
detailsViewModel.getArtistInfo(name, lang, null)
.observe(viewLifecycleOwner, { result ->
when (result) {
is Result.Loading -> println("Loading")
is Result.Error -> println("Error")
is Result.Success -> artistInfo(result.data)
}
})
}
private fun artistInfo(lastFmArtist: LastFmArtist?) {
if (lastFmArtist != null && lastFmArtist.artist != null) {
val bioContent = lastFmArtist.artist.bio.content
if (bioContent != null && bioContent.trim { it <= ' ' }.isNotEmpty()) {
biographyText.visibility = View.VISIBLE
biographyTitle.visibility = View.VISIBLE
biography = HtmlCompat.fromHtml(bioContent, HtmlCompat.FROM_HTML_MODE_LEGACY)
biographyText.text = biography
if (lastFmArtist.artist.stats.listeners.isNotEmpty()) {
listeners.show()
listenersLabel.show()
scrobbles.show()
scrobblesLabel.show()
listeners.text =
RetroUtil.formatValue(lastFmArtist.artist.stats.listeners.toFloat())
scrobbles.text =
RetroUtil.formatValue(lastFmArtist.artist.stats.playcount.toFloat())
}
}
}
// If the "lang" parameter is set and no biography is given, retry with default language
if (biography == null && lang != null) {
loadBiography(artist.name, null)
}
}
private fun loadArtistImage(artist: Artist) {
ArtistGlideRequest.Builder.from(Glide.with(requireContext()), artist)
.generatePalette(requireContext()).build()
.dontAnimate()
.into(object : SingleColorTarget(image) {
override fun onColorReady(color: Int) {
setColors(color)
}
})
}
private fun setColors(color: Int) {
shuffleAction?.applyColor(color)
playAction?.applyOutlineColor(color)
}
override fun onAlbumClick(albumId: Long, view: View) {
exitTransition = MaterialElevationScale(false).apply {
duration = 300L
}
reenterTransition = MaterialElevationScale(false).apply {
duration = 300L
}
findNavController().navigate(
R.id.albumDetailsFragment,
bundleOf(EXTRA_ALBUM_ID to albumId),
null,
FragmentNavigatorExtras(
view to "album"
)
)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return handleSortOrderMenuItem(item)
}
private fun handleSortOrderMenuItem(item: MenuItem): Boolean {
val songs = artist.songs
when (item.itemId) {
android.R.id.home -> findNavController().navigateUp()
R.id.action_play_next -> {
MusicPlayerRemote.playNext(songs)
return true
}
R.id.action_add_to_current_playing -> {
MusicPlayerRemote.enqueue(songs)
return true
}
R.id.action_add_to_playlist -> {
lifecycleScope.launch(Dispatchers.IO) {
val playlists = get<RealRepository>().fetchPlaylists()
withContext(Dispatchers.Main) {
AddToPlaylistDialog.create(playlists, songs)
.show(childFragmentManager, "ADD_PLAYLIST")
}
}
return true
}
R.id.action_set_artist_image -> {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "image/*"
startActivityForResult(
Intent.createChooser(intent, getString(R.string.pick_from_local_storage)),
REQUEST_CODE_SELECT_IMAGE
)
return true
}
R.id.action_reset_artist_image -> {
showToast(resources.getString(R.string.updating))
CustomArtistImageUtil.getInstance(requireContext()).resetCustomArtistImage(artist)
forceDownload = true
return true
}
}
return true
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQUEST_CODE_SELECT_IMAGE -> if (resultCode == Activity.RESULT_OK) {
data?.data?.let {
CustomArtistImageUtil.getInstance(requireContext())
.setCustomArtistImage(artist, it)
}
}
else -> if (resultCode == Activity.RESULT_OK) {
println("OK")
}
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.menu_artist_detail, menu)
}
companion object {
const val REQUEST_CODE_SELECT_IMAGE = 9002
}
} }

View file

@ -14,11 +14,7 @@
*/ */
package code.name.monkey.retromusic.fragments.artists package code.name.monkey.retromusic.fragments.artists
import androidx.lifecycle.LiveData import androidx.lifecycle.*
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.liveData
import androidx.lifecycle.viewModelScope
import code.name.monkey.retromusic.interfaces.IMusicServiceEventListener import code.name.monkey.retromusic.interfaces.IMusicServiceEventListener
import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.network.Result import code.name.monkey.retromusic.network.Result
@ -29,7 +25,8 @@ import kotlinx.coroutines.launch
class ArtistDetailsViewModel( class ArtistDetailsViewModel(
private val realRepository: RealRepository, private val realRepository: RealRepository,
private val artistId: Long private val artistId: Long?,
private val artistName: String?
) : ViewModel(), IMusicServiceEventListener { ) : ViewModel(), IMusicServiceEventListener {
private val artistDetails = MutableLiveData<Artist>() private val artistDetails = MutableLiveData<Artist>()
@ -39,7 +36,9 @@ class ArtistDetailsViewModel(
private fun fetchArtist() { private fun fetchArtist() {
viewModelScope.launch(IO) { viewModelScope.launch(IO) {
artistDetails.postValue(realRepository.artistById(artistId)) artistId?.let { artistDetails.postValue(realRepository.artistById(it)) }
artistName?.let { artistDetails.postValue(realRepository.albumArtistByName(it)) }
} }
} }
@ -66,4 +65,5 @@ class ArtistDetailsViewModel(
override fun onPlayStateChanged() {} override fun onPlayStateChanged() {}
override fun onRepeatModeChanged() {} override fun onRepeatModeChanged() {}
override fun onShuffleModeChanged() {} override fun onShuffleModeChanged() {}
override fun onFavoriteStateChanged() {}
} }

View file

@ -16,38 +16,48 @@ package code.name.monkey.retromusic.fragments.artists
import android.os.Bundle import android.os.Bundle
import android.view.* import android.view.*
import androidx.activity.addCallback
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.lifecycle.Observer
import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import code.name.monkey.retromusic.EXTRA_ARTIST_ID import code.name.monkey.retromusic.EXTRA_ARTIST_ID
import code.name.monkey.retromusic.EXTRA_ARTIST_NAME
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.artist.ArtistAdapter import code.name.monkey.retromusic.adapter.artist.ArtistAdapter
import code.name.monkey.retromusic.extensions.surfaceColor import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.fragments.ReloadType import code.name.monkey.retromusic.fragments.ReloadType
import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewCustomGridSizeFragment import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewCustomGridSizeFragment
import code.name.monkey.retromusic.helper.SortOrder.ArtistSortOrder import code.name.monkey.retromusic.helper.SortOrder.ArtistSortOrder
import code.name.monkey.retromusic.interfaces.IAlbumArtistClickListener
import code.name.monkey.retromusic.interfaces.IArtistClickListener import code.name.monkey.retromusic.interfaces.IArtistClickListener
import code.name.monkey.retromusic.interfaces.ICabHolder import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroColorUtil import code.name.monkey.retromusic.util.RetroColorUtil
import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil
import com.afollestad.materialcab.MaterialCab import com.afollestad.materialcab.MaterialCab
import com.google.android.material.transition.MaterialElevationScale import com.google.android.gms.cast.framework.CastButtonFactory
class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment<ArtistAdapter, GridLayoutManager>(), class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment<ArtistAdapter, GridLayoutManager>(),
IArtistClickListener, ICabHolder { IArtistClickListener, IAlbumArtistClickListener, ICabHolder {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
libraryViewModel.getArtists().observe(viewLifecycleOwner, Observer { libraryViewModel.getArtists().observe(viewLifecycleOwner, {
if (it.isNotEmpty()) if (it.isNotEmpty())
adapter?.swapDataSet(it) adapter?.swapDataSet(it)
else else
adapter?.swapDataSet(listOf()) adapter?.swapDataSet(listOf())
}) })
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
if (!handleBackPress()) {
remove()
requireActivity().onBackPressed()
}
}
} }
override val titleRes: Int
get() = R.string.artists
override val emptyMessage: Int override val emptyMessage: Int
get() = R.string.no_artists get() = R.string.no_artists
@ -66,7 +76,7 @@ class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment<ArtistAdapter, Gri
dataSet, dataSet,
itemLayoutRes(), itemLayoutRes(),
this, this,
this this, this
) )
} }
@ -115,18 +125,23 @@ class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment<ArtistAdapter, Gri
} }
override fun onArtist(artistId: Long, view: View) { override fun onArtist(artistId: Long, view: View) {
exitTransition = MaterialElevationScale(true).apply {
duration = 300L
}
reenterTransition = MaterialElevationScale(false).apply {
duration = 300L
}
findNavController().navigate( findNavController().navigate(
R.id.artistDetailsFragment, R.id.artistDetailsFragment,
bundleOf(EXTRA_ARTIST_ID to artistId), bundleOf(EXTRA_ARTIST_ID to artistId),
null, null,
FragmentNavigatorExtras(view to "artist") FragmentNavigatorExtras(view to artistId.toString())
) )
reenterTransition = null
}
override fun onAlbumArtist(artistName: String, view: View) {
findNavController().navigate(
R.id.albumArtistDetailsFragment,
bundleOf(EXTRA_ARTIST_NAME to artistName),
null,
FragmentNavigatorExtras(view to artistName)
)
reenterTransition = null
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
@ -139,6 +154,16 @@ class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment<ArtistAdapter, Gri
val layoutItem = menu.findItem(R.id.action_layout_type) val layoutItem = menu.findItem(R.id.action_layout_type)
setupLayoutMenu(layoutItem.subMenu) setupLayoutMenu(layoutItem.subMenu)
setUpSortOrderMenu(menu.findItem(R.id.action_sort_order).subMenu) setUpSortOrderMenu(menu.findItem(R.id.action_sort_order).subMenu)
setupAlbumArtistMenu(menu)
//Setting up cast button
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.action_cast)
}
private fun setupAlbumArtistMenu(menu: Menu) {
menu.add(0, R.id.action_album_artist, 0, R.string.show_album_artists).apply {
isCheckable = true
isChecked = PreferenceUtil.albumArtistsOnly
}
} }
private fun setUpSortOrderMenu( private fun setUpSortOrderMenu(
@ -222,9 +247,23 @@ class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment<ArtistAdapter, Gri
if (handleSortOrderMenuItem(item)) { if (handleSortOrderMenuItem(item)) {
return true return true
} }
if (handleAlbumArtistMenu(item)) {
return true
}
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
private fun handleAlbumArtistMenu(item: MenuItem): Boolean {
return if (item.itemId == R.id.action_album_artist) {
PreferenceUtil.albumArtistsOnly = !item.isChecked
item.isChecked = !item.isChecked
libraryViewModel.forceReload(ReloadType.Artists)
true
} else {
false
}
}
private fun handleSortOrderMenuItem( private fun handleSortOrderMenuItem(
item: MenuItem item: MenuItem
): Boolean { ): Boolean {
@ -283,6 +322,16 @@ class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment<ArtistAdapter, Gri
return false return false
} }
private fun handleBackPress(): Boolean {
cab?.let {
if (it.isActive) {
it.finish()
return true
}
}
return false
}
private var cab: MaterialCab? = null private var cab: MaterialCab? = null
override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab { override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab {
@ -298,4 +347,9 @@ class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment<ArtistAdapter, Gri
.start(callback) .start(callback)
return cab as MaterialCab return cab as MaterialCab
} }
override fun onResume() {
super.onResume()
libraryViewModel.forceReload(ReloadType.Artists)
}
} }

View file

@ -27,10 +27,9 @@ import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.interfaces.IMusicServiceEventListener import code.name.monkey.retromusic.interfaces.IMusicServiceEventListener
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil
import org.jaudiotagger.audio.AudioFileIO
import java.io.File import java.io.File
import java.net.URLEncoder import java.net.URLEncoder
import java.util.*
import org.jaudiotagger.audio.AudioFileIO
/** /**
* Created by hemanths on 18/08/17. * Created by hemanths on 18/08/17.
@ -78,6 +77,9 @@ open class AbsMusicServiceFragment(@LayoutRes layout: Int) : Fragment(layout),
serviceActivity?.removeMusicServiceEventListener(this) serviceActivity?.removeMusicServiceEventListener(this)
} }
override fun onFavoriteStateChanged() {
}
override fun onPlayingMetaChanged() { override fun onPlayingMetaChanged() {
} }
@ -121,10 +123,10 @@ open class AbsMusicServiceFragment(@LayoutRes layout: Int) : Fragment(layout),
return "-" return "-"
} }
private fun getMimeType(url: String): String? { private fun getMimeType(url: String): String {
var type: String? = MimeTypeMap.getFileExtensionFromUrl( var type: String? = MimeTypeMap.getFileExtensionFromUrl(
URLEncoder.encode(url, "utf-8") URLEncoder.encode(url, "utf-8")
).toUpperCase(Locale.getDefault()) ).uppercase()
if (type == null) { if (type == null) {
type = url.substring(url.lastIndexOf(".") + 1) type = url.substring(url.lastIndexOf(".") + 1)
} }

View file

@ -44,7 +44,7 @@ abstract class AbsPlayerControlsFragment(@LayoutRes layout: Int) : AbsMusicServi
abstract fun setColor(color: MediaNotificationProcessor) abstract fun setColor(color: MediaNotificationProcessor)
fun showBonceAnimation(view: View) { fun showBounceAnimation(view: View) {
view.apply { view.apply {
clearAnimation() clearAnimation()
scaleX = 0.9f scaleX = 0.9f

View file

@ -14,31 +14,41 @@
*/ */
package code.name.monkey.retromusic.fragments.base package code.name.monkey.retromusic.fragments.base
import android.annotation.SuppressLint
import android.app.Activity
import android.content.ContentUris import android.content.ContentUris
import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.media.MediaMetadataRetriever import android.media.MediaMetadataRetriever
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.MediaStore import android.provider.MediaStore
import android.text.TextUtils import android.text.TextUtils
import android.view.GestureDetector
import android.view.MenuItem import android.view.MenuItem
import android.view.MotionEvent
import android.view.View import android.view.View
import android.widget.RelativeLayout
import android.widget.Toast import android.widget.Toast
import androidx.annotation.LayoutRes import androidx.annotation.LayoutRes
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.viewpager.widget.ViewPager
import code.name.monkey.retromusic.EXTRA_ALBUM_ID import code.name.monkey.retromusic.EXTRA_ALBUM_ID
import code.name.monkey.retromusic.EXTRA_ARTIST_ID import code.name.monkey.retromusic.EXTRA_ARTIST_ID
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.activities.tageditor.AbsTagEditorActivity import code.name.monkey.retromusic.activities.tageditor.AbsTagEditorActivity
import code.name.monkey.retromusic.activities.tageditor.SongTagEditorActivity import code.name.monkey.retromusic.activities.tageditor.SongTagEditorActivity
import code.name.monkey.retromusic.db.PlaylistEntity import code.name.monkey.retromusic.db.PlaylistEntity
import code.name.monkey.retromusic.db.SongEntity import code.name.monkey.retromusic.db.SongEntity
import code.name.monkey.retromusic.db.toSongEntity import code.name.monkey.retromusic.db.toSongEntity
import code.name.monkey.retromusic.dialogs.* import code.name.monkey.retromusic.dialogs.*
import code.name.monkey.retromusic.extensions.currentFragment
import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.whichFragment import code.name.monkey.retromusic.extensions.whichFragment
import code.name.monkey.retromusic.fragments.ReloadType import code.name.monkey.retromusic.fragments.ReloadType
@ -50,13 +60,14 @@ import code.name.monkey.retromusic.model.lyrics.Lyrics
import code.name.monkey.retromusic.repository.RealRepository import code.name.monkey.retromusic.repository.RealRepository
import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.* import code.name.monkey.retromusic.util.*
import kotlinx.android.synthetic.main.shadow_statusbar_toolbar.* import com.google.android.material.bottomsheet.BottomSheetBehavior
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koin.android.ext.android.get import org.koin.android.ext.android.get
import java.io.FileNotFoundException import java.io.FileNotFoundException
import kotlin.math.abs
abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragment(layout), abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragment(layout),
Toolbar.OnMenuItemClickListener, IPaletteColorHolder, PlayerAlbumCoverFragment.Callbacks { Toolbar.OnMenuItemClickListener, IPaletteColorHolder, PlayerAlbumCoverFragment.Callbacks {
@ -68,6 +79,11 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
): Boolean { ): Boolean {
val song = MusicPlayerRemote.currentSong val song = MusicPlayerRemote.currentSong
when (item.itemId) { when (item.itemId) {
R.id.action_toggle_lyrics -> {
PreferenceUtil.showLyrics = !PreferenceUtil.showLyrics
showLyricsIcon(item)
return true
}
R.id.action_toggle_favorite -> { R.id.action_toggle_favorite -> {
toggleFavorite(song) toggleFavorite(song)
return true return true
@ -114,6 +130,8 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
return true return true
} }
R.id.action_go_to_album -> { R.id.action_go_to_album -> {
//Hide Bottom Bar First, else Bottom Sheet doesn't collapse fully
mainActivity.setBottomBarVisibility(false)
mainActivity.collapsePanel() mainActivity.collapsePanel()
requireActivity().findNavController(R.id.fragment_container).navigate( requireActivity().findNavController(R.id.fragment_container).navigate(
R.id.albumDetailsFragment, R.id.albumDetailsFragment,
@ -122,11 +140,7 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
return true return true
} }
R.id.action_go_to_artist -> { R.id.action_go_to_artist -> {
mainActivity.collapsePanel() goToArtist(requireActivity())
requireActivity().findNavController(R.id.fragment_container).navigate(
R.id.artistDetailsFragment,
bundleOf(EXTRA_ARTIST_ID to song.artistId)
)
return true return true
} }
R.id.now_playing -> { R.id.now_playing -> {
@ -158,7 +172,7 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
val trackUri = val trackUri =
ContentUris.withAppendedId( ContentUris.withAppendedId(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
song.id.toLong() song.id
) )
retriever.setDataSource(activity, trackUri) retriever.setDataSource(activity, trackUri)
var genre: String? = var genre: String? =
@ -173,6 +187,18 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
return false return false
} }
private fun showLyricsIcon(item: MenuItem) {
val icon =
if (PreferenceUtil.showLyrics) R.drawable.ic_lyrics else R.drawable.ic_lyrics_outline
val drawable: Drawable? = RetroUtil.getTintedVectorDrawable(
requireContext(),
icon,
toolbarIconColor()
)
item.isChecked = PreferenceUtil.showLyrics
item.icon = drawable
}
abstract fun playerToolbar(): Toolbar? abstract fun playerToolbar(): Toolbar?
abstract fun onShow() abstract fun onShow()
@ -185,7 +211,6 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
override fun onServiceConnected() { override fun onServiceConnected() {
updateIsFavorite() updateIsFavorite()
updateLyrics()
} }
override fun onPlayingMetaChanged() { override fun onPlayingMetaChanged() {
@ -193,9 +218,13 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
updateLyrics() updateLyrics()
} }
override fun onFavoriteStateChanged() {
updateIsFavorite(animate = true)
}
protected open fun toggleFavorite(song: Song) { protected open fun toggleFavorite(song: Song) {
lifecycleScope.launch(IO) { lifecycleScope.launch(IO) {
val playlist: PlaylistEntity? = libraryViewModel.favoritePlaylist() val playlist: PlaylistEntity = libraryViewModel.favoritePlaylist()
if (playlist != null) { if (playlist != null) {
val songEntity = song.toSongEntity(playlist.playListId) val songEntity = song.toSongEntity(playlist.playListId)
val isFavorite = libraryViewModel.isFavoriteSong(songEntity).isNotEmpty() val isFavorite = libraryViewModel.isFavoriteSong(songEntity).isNotEmpty()
@ -210,26 +239,36 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
} }
} }
fun updateIsFavorite() { fun updateIsFavorite(animate: Boolean = false) {
lifecycleScope.launch(IO) { lifecycleScope.launch(IO) {
val playlist: PlaylistEntity? = libraryViewModel.favoritePlaylist() val playlist: PlaylistEntity = libraryViewModel.favoritePlaylist()
if (playlist != null) { if (playlist != null) {
val song: SongEntity = val song: SongEntity =
MusicPlayerRemote.currentSong.toSongEntity(playlist.playListId) MusicPlayerRemote.currentSong.toSongEntity(playlist.playListId)
val isFavorite: Boolean = libraryViewModel.isFavoriteSong(song).isNotEmpty() val isFavorite: Boolean = libraryViewModel.isFavoriteSong(song).isNotEmpty()
withContext(Main) { withContext(Main) {
val icon = val icon = if (animate) {
if (isFavorite) R.drawable.avd_favorite else R.drawable.avd_unfavorite
} else {
if (isFavorite) R.drawable.ic_favorite else R.drawable.ic_favorite_border if (isFavorite) R.drawable.ic_favorite else R.drawable.ic_favorite_border
}
val drawable: Drawable? = RetroUtil.getTintedVectorDrawable( val drawable: Drawable? = RetroUtil.getTintedVectorDrawable(
requireContext(), requireContext(),
icon, icon,
toolbarIconColor() toolbarIconColor()
) )
if (playerToolbar() != null) { if (playerToolbar() != null) {
playerToolbar()?.menu?.findItem(R.id.action_toggle_favorite) playerToolbar()?.menu?.findItem(R.id.action_toggle_favorite)?.apply {
?.setIcon(drawable)?.title = setIcon(drawable)
title =
if (isFavorite) getString(R.string.action_remove_from_favorites) if (isFavorite) getString(R.string.action_remove_from_favorites)
else getString(R.string.action_add_to_favorites) else getString(R.string.action_add_to_favorites)
getIcon().also {
if (it is AnimatedVectorDrawable) {
it.start()
}
}
}
} }
} }
} }
@ -273,7 +312,50 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
playerAlbumCoverFragment?.setCallbacks(this) playerAlbumCoverFragment?.setCallbacks(this)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
statusBarShadow?.hide() view.findViewById<RelativeLayout>(R.id.statusBarShadow)?.hide()
}
@SuppressLint("ClickableViewAccessibility")
override fun onStart() {
super.onStart()
requireView().setOnTouchListener(
SwipeDetector(
requireContext(),
playerAlbumCoverFragment?.viewPager,
requireView()
)
)
}
class SwipeDetector(val context: Context, val viewPager: ViewPager?, val view: View) :
View.OnTouchListener {
private var flingPlayBackController: GestureDetector = GestureDetector(
context,
object : GestureDetector.SimpleOnGestureListener() {
override fun onScroll(
e1: MotionEvent?,
e2: MotionEvent?,
distanceX: Float,
distanceY: Float
): Boolean {
return when {
abs(distanceX) > abs(distanceY) -> {
// Disallow Intercept Touch Event so that parent(BottomSheet) doesn't consume the events
view.parent.requestDisallowInterceptTouchEvent(true)
true
}
else -> {
false
}
}
}
})
@SuppressLint("ClickableViewAccessibility")
override fun onTouch(v: View, event: MotionEvent?): Boolean {
viewPager?.dispatchTouchEvent(event)
return flingPlayBackController.onTouchEvent(event)
}
} }
companion object { companion object {
@ -290,3 +372,44 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
) )
} }
} }
fun goToArtist(activity: Activity) {
if (activity !is MainActivity) return
val song = MusicPlayerRemote.currentSong
activity.apply {
// Remove exit transition of current fragment so
// it doesn't exit with a weird transition
currentFragment(R.id.fragment_container)?.exitTransition = null
//Hide Bottom Bar First, else Bottom Sheet doesn't collapse fully
setBottomBarVisibility(false)
if (getBottomSheetBehavior().state == BottomSheetBehavior.STATE_EXPANDED) {
collapsePanel()
}
findNavController(R.id.fragment_container).navigate(
R.id.artistDetailsFragment,
bundleOf(EXTRA_ARTIST_ID to song.artistId)
)
}
}
fun goToAlbum(activity: Activity) {
if (activity !is MainActivity) return
val song = MusicPlayerRemote.currentSong
activity.apply {
currentFragment(R.id.fragment_container)?.exitTransition = null
//Hide Bottom Bar First, else Bottom Sheet doesn't collapse fully
setBottomBarVisibility(false)
if (getBottomSheetBehavior().state == BottomSheetBehavior.STATE_EXPANDED) {
collapsePanel()
}
findNavController(R.id.fragment_container).navigate(
R.id.albumDetailsFragment,
bundleOf(EXTRA_ALBUM_ID to song.albumId)
)
}
}

View file

@ -14,12 +14,12 @@
*/ */
package code.name.monkey.retromusic.fragments.base package code.name.monkey.retromusic.fragments.base
import android.os.Bundle import androidx.core.view.isVisible
import android.view.View
import androidx.annotation.LayoutRes
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.transition.TransitionManager
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil
import com.google.android.material.transition.MaterialFade
abstract class AbsRecyclerViewCustomGridSizeFragment<A : RecyclerView.Adapter<*>, LM : RecyclerView.LayoutManager> : abstract class AbsRecyclerViewCustomGridSizeFragment<A : RecyclerView.Adapter<*>, LM : RecyclerView.LayoutManager> :
AbsRecyclerViewFragment<A, LM>() { AbsRecyclerViewFragment<A, LM>() {
@ -86,6 +86,7 @@ abstract class AbsRecyclerViewCustomGridSizeFragment<A : RecyclerView.Adapter<*>
} else { } else {
saveGridSize(gridSize) saveGridSize(gridSize)
} }
recyclerView().isVisible = false
invalidateLayoutManager() invalidateLayoutManager()
// only recreate the adapter and layout manager if the layout currentLayoutRes has changed // only recreate the adapter and layout manager if the layout currentLayoutRes has changed
if (oldLayoutRes != itemLayoutRes()) { if (oldLayoutRes != itemLayoutRes()) {
@ -93,26 +94,11 @@ abstract class AbsRecyclerViewCustomGridSizeFragment<A : RecyclerView.Adapter<*>
} else { } else {
setGridSize(gridSize) setGridSize(gridSize)
} }
val transition = MaterialFade().apply {
addTarget(recyclerView())
} }
TransitionManager.beginDelayedTransition(getContainer(), transition)
protected fun notifyLayoutResChanged(@LayoutRes res: Int) { recyclerView().isVisible = true
this.currentLayoutRes = res
val recyclerView = recyclerView()
applyRecyclerViewPaddingForLayoutRes(recyclerView, currentLayoutRes)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
applyRecyclerViewPaddingForLayoutRes(recyclerView(), currentLayoutRes)
}
private fun applyRecyclerViewPaddingForLayoutRes(recyclerView: RecyclerView, res: Int) {
val padding: Int = if (res == R.layout.item_grid) {
(resources.displayMetrics.density * 2).toInt()
} else {
0
}
//recyclerView.setPadding(padding, padding, padding, padding)
} }
protected abstract fun setGridSize(gridSize: Int) protected abstract fun setGridSize(gridSize: Int)

View file

@ -15,80 +15,73 @@
package code.name.monkey.retromusic.fragments.base package code.name.monkey.retromusic.fragments.base
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.*
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.annotation.NonNull import androidx.annotation.NonNull
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.core.text.HtmlCompat import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.doOnPreDraw import androidx.core.view.doOnPreDraw
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.common.ATHToolbarActivity import code.name.monkey.appthemehelper.common.ATHToolbarActivity
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentMainRecyclerBinding
import code.name.monkey.retromusic.dialogs.CreatePlaylistDialog import code.name.monkey.retromusic.dialogs.CreatePlaylistDialog
import code.name.monkey.retromusic.dialogs.ImportPlaylistDialog import code.name.monkey.retromusic.dialogs.ImportPlaylistDialog
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.util.DensityUtil import code.name.monkey.retromusic.util.DensityUtil
import code.name.monkey.retromusic.util.ThemedFastScroller.create import code.name.monkey.retromusic.util.ThemedFastScroller.create
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.transition.MaterialFadeThrough
import kotlinx.android.synthetic.main.fragment_main_recycler.* import com.google.android.material.transition.MaterialSharedAxis
import me.zhanghai.android.fastscroll.FastScroller import me.zhanghai.android.fastscroll.FastScroller
import me.zhanghai.android.fastscroll.FastScrollerBuilder import me.zhanghai.android.fastscroll.FastScrollerBuilder
abstract class AbsRecyclerViewFragment<A : RecyclerView.Adapter<*>, LM : RecyclerView.LayoutManager> : abstract class AbsRecyclerViewFragment<A : RecyclerView.Adapter<*>, LM : RecyclerView.LayoutManager> :
AbsMainActivityFragment(R.layout.fragment_main_recycler), AbsMainActivityFragment(R.layout.fragment_main_recycler) {
AppBarLayout.OnOffsetChangedListener {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setHasOptionsMenu(true)
}
private var _binding: FragmentMainRecyclerBinding? = null
private val binding get() = _binding!!
protected var adapter: A? = null protected var adapter: A? = null
protected var layoutManager: LM? = null protected var layoutManager: LM? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
_binding = FragmentMainRecyclerBinding.bind(view)
enterTransition = MaterialFadeThrough()
exitTransition = MaterialFadeThrough()
postponeEnterTransition() postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() } view.doOnPreDraw { startPostponedEnterTransition() }
mainActivity.setBottomBarVisibility(true) mainActivity.setSupportActionBar(binding.toolbar)
mainActivity.setSupportActionBar(toolbar)
mainActivity.supportActionBar?.title = null mainActivity.supportActionBar?.title = null
initLayoutManager() initLayoutManager()
initAdapter() initAdapter()
setUpRecyclerView() setUpRecyclerView()
setupTitle() setupToolbar()
} }
private fun setupTitle() { private fun setupToolbar() {
toolbar.setNavigationOnClickListener { binding.toolbar.setNavigationOnClickListener {
exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true).addTarget(requireView())
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
findNavController().navigate( findNavController().navigate(
R.id.searchFragment, R.id.searchFragment,
null, null,
navOptions navOptions
) )
} }
val color = ThemeStore.accentColor(requireContext()) val appName = resources.getString(titleRes)
val hexColor = String.format("#%06X", 0xFFFFFF and color) binding.appNameText.text = appName
val appName = HtmlCompat.fromHtml(
"Retro <span style='color:$hexColor';>Music</span>",
HtmlCompat.FROM_HTML_MODE_COMPACT
)
appNameText.text = appName
} }
abstract val titleRes: Int
private fun setUpRecyclerView() { private fun setUpRecyclerView() {
recyclerView.apply { binding.recyclerView.apply {
layoutManager = this@AbsRecyclerViewFragment.layoutManager layoutManager = this@AbsRecyclerViewFragment.layoutManager
adapter = this@AbsRecyclerViewFragment.adapter adapter = this@AbsRecyclerViewFragment.adapter
val fastScroller = create(this) create(this)
} }
checkForPadding() checkForPadding()
} }
@ -116,8 +109,8 @@ abstract class AbsRecyclerViewFragment<A : RecyclerView.Adapter<*>, LM : Recycle
} }
private fun checkIsEmpty() { private fun checkIsEmpty() {
emptyText.setText(emptyMessage) binding.emptyText.setText(emptyMessage)
empty.visibility = if (adapter!!.itemCount == 0) View.VISIBLE else View.GONE binding.empty.visibility = if (adapter!!.itemCount == 0) View.VISIBLE else View.GONE
} }
private fun checkForPadding() { private fun checkForPadding() {
@ -125,10 +118,10 @@ abstract class AbsRecyclerViewFragment<A : RecyclerView.Adapter<*>, LM : Recycle
if (itemCount > 0 && MusicPlayerRemote.playingQueue.isNotEmpty()) { if (itemCount > 0 && MusicPlayerRemote.playingQueue.isNotEmpty()) {
val height = DensityUtil.dip2px(requireContext(), 112f) val height = DensityUtil.dip2px(requireContext(), 112f)
recyclerView.updatePadding(0, 0, 0, height) binding.recyclerView.updatePadding(0, 0, 0, height)
} else { } else {
val height = DensityUtil.dip2px(requireContext(), 56f) val height = DensityUtil.dip2px(requireContext(), 56f)
recyclerView.updatePadding(0, 0, 0, height) binding.recyclerView.updatePadding(0, 0, 0, height)
} }
} }
@ -141,15 +134,6 @@ abstract class AbsRecyclerViewFragment<A : RecyclerView.Adapter<*>, LM : Recycle
@NonNull @NonNull
protected abstract fun createAdapter(): A protected abstract fun createAdapter(): A
override fun onOffsetChanged(p0: AppBarLayout?, i: Int) {
/*recyclerView.setPadding(
recyclerView.paddingLeft,
recyclerView.paddingTop,
recyclerView.paddingRight,
i
)*/
}
override fun onQueueChanged() { override fun onQueueChanged() {
super.onQueueChanged() super.onQueueChanged()
checkForPadding() checkForPadding()
@ -162,22 +146,31 @@ abstract class AbsRecyclerViewFragment<A : RecyclerView.Adapter<*>, LM : Recycle
protected fun invalidateLayoutManager() { protected fun invalidateLayoutManager() {
initLayoutManager() initLayoutManager()
recyclerView.layoutManager = layoutManager binding.recyclerView.layoutManager = layoutManager
} }
protected fun invalidateAdapter() { protected fun invalidateAdapter() {
initAdapter() initAdapter()
checkIsEmpty() checkIsEmpty()
recyclerView.adapter = adapter binding.recyclerView.adapter = adapter
} }
fun recyclerView(): RecyclerView { fun recyclerView(): RecyclerView {
return recyclerView return binding.recyclerView
}
fun getContainer(): CoordinatorLayout {
return binding.root
}
fun scrollToTop() {
recyclerView().scrollToPosition(0)
binding.appBarLayout.setExpanded(true, true)
} }
override fun onPrepareOptionsMenu(menu: Menu) { override fun onPrepareOptionsMenu(menu: Menu) {
super.onPrepareOptionsMenu(menu) super.onPrepareOptionsMenu(menu)
ToolbarContentTintHelper.handleOnPrepareOptionsMenu(requireActivity(), toolbar) ToolbarContentTintHelper.handleOnPrepareOptionsMenu(requireActivity(), binding.toolbar)
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
@ -185,9 +178,9 @@ abstract class AbsRecyclerViewFragment<A : RecyclerView.Adapter<*>, LM : Recycle
inflater.inflate(R.menu.menu_main, menu) inflater.inflate(R.menu.menu_main, menu)
ToolbarContentTintHelper.handleOnCreateOptionsMenu( ToolbarContentTintHelper.handleOnCreateOptionsMenu(
requireContext(), requireContext(),
toolbar, binding.toolbar,
menu, menu,
ATHToolbarActivity.getToolbarBackgroundColor(toolbar) ATHToolbarActivity.getToolbarBackgroundColor(binding.toolbar)
) )
} }
@ -209,4 +202,9 @@ abstract class AbsRecyclerViewFragment<A : RecyclerView.Adapter<*>, LM : Recycle
} }
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
} }

View file

@ -14,13 +14,14 @@
package code.name.monkey.retromusic.fragments.folder; package code.name.monkey.retromusic.fragments.folder;
import static code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackgroundColor;
import android.app.Dialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.media.MediaScannerConnection; import android.media.MediaScannerConnection;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment; import android.os.Environment;
import android.text.Html; import android.text.Html;
import android.text.Spanned;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
@ -30,13 +31,11 @@ import android.view.ViewGroup;
import android.view.ViewGroup.MarginLayoutParams; import android.view.ViewGroup.MarginLayoutParams;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
import android.widget.PopupMenu; import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.core.text.HtmlCompat;
import androidx.loader.app.LoaderManager; import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader; import androidx.loader.content.Loader;
import androidx.navigation.Navigation; import androidx.navigation.Navigation;
@ -46,25 +45,36 @@ import androidx.recyclerview.widget.RecyclerView;
import com.afollestad.materialcab.MaterialCab; import com.afollestad.materialcab.MaterialCab;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.transition.MaterialFadeThrough;
import com.google.android.material.transition.MaterialSharedAxis;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.StringTokenizer;
import code.name.monkey.appthemehelper.ThemeStore; import code.name.monkey.appthemehelper.ThemeStore;
import code.name.monkey.appthemehelper.util.ATHUtil; import code.name.monkey.appthemehelper.util.ATHUtil;
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper;
import code.name.monkey.retromusic.App;
import code.name.monkey.retromusic.R; import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.adapter.SongFileAdapter; import code.name.monkey.retromusic.adapter.SongFileAdapter;
import code.name.monkey.retromusic.adapter.Storage;
import code.name.monkey.retromusic.adapter.StorageAdapter;
import code.name.monkey.retromusic.adapter.StorageClickListener;
import code.name.monkey.retromusic.databinding.FragmentFolderBinding;
import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment; import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment;
import code.name.monkey.retromusic.helper.MusicPlayerRemote; import code.name.monkey.retromusic.helper.MusicPlayerRemote;
import code.name.monkey.retromusic.helper.menu.SongMenuHelper; import code.name.monkey.retromusic.helper.menu.SongMenuHelper;
@ -76,6 +86,7 @@ import code.name.monkey.retromusic.misc.DialogAsyncTask;
import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener; import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener;
import code.name.monkey.retromusic.misc.WrappedAsyncTaskLoader; import code.name.monkey.retromusic.misc.WrappedAsyncTaskLoader;
import code.name.monkey.retromusic.model.Song; import code.name.monkey.retromusic.model.Song;
import code.name.monkey.retromusic.providers.BlacklistStore;
import code.name.monkey.retromusic.util.DensityUtil; import code.name.monkey.retromusic.util.DensityUtil;
import code.name.monkey.retromusic.util.FileUtil; import code.name.monkey.retromusic.util.FileUtil;
import code.name.monkey.retromusic.util.PreferenceUtil; import code.name.monkey.retromusic.util.PreferenceUtil;
@ -85,15 +96,14 @@ import code.name.monkey.retromusic.views.BreadCrumbLayout;
import code.name.monkey.retromusic.views.ScrollingViewOnApplyWindowInsetsListener; import code.name.monkey.retromusic.views.ScrollingViewOnApplyWindowInsetsListener;
import me.zhanghai.android.fastscroll.FastScroller; import me.zhanghai.android.fastscroll.FastScroller;
import static code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackgroundColor;
public class FoldersFragment extends AbsMainActivityFragment public class FoldersFragment extends AbsMainActivityFragment
implements IMainActivityFragmentCallbacks, implements IMainActivityFragmentCallbacks,
ICabHolder, ICabHolder,
BreadCrumbLayout.SelectionCallback, BreadCrumbLayout.SelectionCallback,
ICallbacks, ICallbacks,
LoaderManager.LoaderCallbacks<List<File>> { LoaderManager.LoaderCallbacks<List<File>>, StorageClickListener {
private FragmentFolderBinding binding;
public static final String TAG = FoldersFragment.class.getSimpleName(); public static final String TAG = FoldersFragment.class.getSimpleName();
public static final FileFilter AUDIO_FILE_FILTER = public static final FileFilter AUDIO_FILE_FILTER =
file -> file ->
@ -106,14 +116,9 @@ public class FoldersFragment extends AbsMainActivityFragment
private static final String CRUMBS = "crumbs"; private static final String CRUMBS = "crumbs";
private static final int LOADER_ID = 5; private static final int LOADER_ID = 5;
private SongFileAdapter adapter; private SongFileAdapter adapter;
private Toolbar toolbar; private StorageAdapter storageAdapter;
private TextView appNameText;
private BreadCrumbLayout breadCrumbs;
private MaterialCab cab; private MaterialCab cab;
private View coordinatorLayout; private final Comparator<File> fileComparator =
private View empty;
private TextView emojiText;
private Comparator<File> fileComparator =
(lhs, rhs) -> { (lhs, rhs) -> {
if (lhs.isDirectory() && !rhs.isDirectory()) { if (lhs.isDirectory() && !rhs.isDirectory()) {
return -1; return -1;
@ -123,7 +128,7 @@ public class FoldersFragment extends AbsMainActivityFragment
return lhs.getName().compareToIgnoreCase(rhs.getName()); return lhs.getName().compareToIgnoreCase(rhs.getName());
} }
}; };
private RecyclerView recyclerView; private final ArrayList<Storage> storageItems = new ArrayList<>();
public FoldersFragment() { public FoldersFragment() {
super(R.layout.fragment_folder); super(R.layout.fragment_folder);
@ -158,35 +163,43 @@ public class FoldersFragment extends AbsMainActivityFragment
@Override @Override
public View onCreateView( public View onCreateView(
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_folder, container, false); binding = FragmentFolderBinding.inflate(inflater, container, false);
initViews(view); return binding.getRoot();
return view;
} }
@Override @Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
setEnterTransition(new MaterialFadeThrough());
setExitTransition(new MaterialFadeThrough());
getMainActivity().addMusicServiceEventListener(getLibraryViewModel()); getMainActivity().addMusicServiceEventListener(getLibraryViewModel());
getMainActivity().setBottomBarVisibility(true); getMainActivity().setSupportActionBar(binding.toolbar);
getMainActivity().setSupportActionBar(toolbar);
getMainActivity().getSupportActionBar().setTitle(null); getMainActivity().getSupportActionBar().setTitle(null);
setStatusBarColorAuto(view); setStatusBarColorAuto(view);
setUpAppbarColor(); setUpAppbarColor();
setUpBreadCrumbs(); setUpBreadCrumbs();
setUpRecyclerView(); setUpRecyclerView();
listRoots();
setUpAdapter(); setUpAdapter();
setUpTitle(); setUpTitle();
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
if (!handleBackPress()) {
remove();
requireActivity().onBackPressed();
}
}
});
} }
private void setUpTitle() { private void setUpTitle() {
toolbar.setNavigationOnClickListener( binding.toolbar.setNavigationOnClickListener(
v -> Navigation.findNavController(v).navigate(R.id.searchFragment, null, getNavOptions())); v -> {
int color = ThemeStore.Companion.accentColor(requireContext()); setExitTransition(new MaterialSharedAxis(MaterialSharedAxis.Z, true).setDuration(300));
String hexColor = String.format("#%06X", 0xFFFFFF & color); setReenterTransition(new MaterialSharedAxis(MaterialSharedAxis.Z, false).setDuration(300));
Spanned appName = Navigation.findNavController(v).navigate(R.id.searchFragment, null, getNavOptions());
HtmlCompat.fromHtml( });
"Retro <span style='color:" + hexColor + ";'>Music</span>", binding.appNameText.setText(getResources().getString(R.string.folders));
HtmlCompat.FROM_HTML_MODE_COMPACT);
appNameText.setText(appName);
} }
@Override @Override
@ -194,12 +207,14 @@ public class FoldersFragment extends AbsMainActivityFragment
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true); setHasOptionsMenu(true);
if (savedInstanceState == null) { if (savedInstanceState == null) {
switchToFileAdapter();
setCrumb( setCrumb(
new BreadCrumbLayout.Crumb( new BreadCrumbLayout.Crumb(
FileUtil.safeGetCanonicalFile(PreferenceUtil.INSTANCE.getStartDirectory())), FileUtil.safeGetCanonicalFile(PreferenceUtil.INSTANCE.getStartDirectory())),
true); true);
} else { } else {
breadCrumbs.restoreFromStateWrapper(savedInstanceState.getParcelable(CRUMBS)); binding.breadCrumbs.restoreFromStateWrapper(savedInstanceState.getParcelable(CRUMBS));
LoaderManager.getInstance(this).initLoader(LOADER_ID, null, this); LoaderManager.getInstance(this).initLoader(LOADER_ID, null, this);
} }
} }
@ -213,8 +228,8 @@ public class FoldersFragment extends AbsMainActivityFragment
@Override @Override
public void onSaveInstanceState(@NonNull Bundle outState) { public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
if (breadCrumbs != null) { if (binding != null) {
outState.putParcelable(CRUMBS, breadCrumbs.getStateWrapper()); outState.putParcelable(CRUMBS, binding.breadCrumbs.getStateWrapper());
} }
} }
@ -224,8 +239,8 @@ public class FoldersFragment extends AbsMainActivityFragment
cab.finish(); cab.finish();
return true; return true;
} }
if (breadCrumbs != null && breadCrumbs.popHistory()) { if (binding.breadCrumbs.popHistory()) {
setCrumb(breadCrumbs.lastHistory(), false); setCrumb(binding.breadCrumbs.lastHistory(), false);
return true; return true;
} }
return false; return false;
@ -268,6 +283,9 @@ public class FoldersFragment extends AbsMainActivityFragment
new ListSongsAsyncTask.LoadingInfo( new ListSongsAsyncTask.LoadingInfo(
toList(file), AUDIO_FILE_FILTER, getFileComparator())); toList(file), AUDIO_FILE_FILTER, getFileComparator()));
return true; return true;
case R.id.action_add_to_blacklist:
BlacklistStore.getInstance(App.Companion.getContext()).addPath(file);
return true;
case R.id.action_set_as_start_directory: case R.id.action_set_as_start_directory:
PreferenceUtil.INSTANCE.setStartDirectory(file); PreferenceUtil.INSTANCE.setStartDirectory(file);
Toast.makeText( Toast.makeText(
@ -347,7 +365,7 @@ public class FoldersFragment extends AbsMainActivityFragment
} else { } else {
final File finalFile = file1; final File finalFile = file1;
Snackbar.make( Snackbar.make(
coordinatorLayout, binding.coordinatorLayout,
Html.fromHtml( Html.fromHtml(
String.format( String.format(
getString(R.string.not_listed_in_media_store), file1.getName())), getString(R.string.not_listed_in_media_store), file1.getName())),
@ -376,7 +394,7 @@ public class FoldersFragment extends AbsMainActivityFragment
@Override @Override
public void onLoaderReset(@NonNull Loader<List<File>> loader) { public void onLoaderReset(@NonNull Loader<List<File>> loader) {
updateAdapter(new LinkedList<File>()); updateAdapter(new LinkedList<>());
} }
@Override @Override
@ -393,7 +411,7 @@ public class FoldersFragment extends AbsMainActivityFragment
@Override @Override
public void onPrepareOptionsMenu(@NonNull Menu menu) { public void onPrepareOptionsMenu(@NonNull Menu menu) {
super.onPrepareOptionsMenu(menu); super.onPrepareOptionsMenu(menu);
ToolbarContentTintHelper.handleOnPrepareOptionsMenu(requireActivity(), toolbar); ToolbarContentTintHelper.handleOnPrepareOptionsMenu(requireActivity(), binding.toolbar);
} }
@Override @Override
@ -407,7 +425,7 @@ public class FoldersFragment extends AbsMainActivityFragment
menu.removeItem(R.id.action_layout_type); menu.removeItem(R.id.action_layout_type);
menu.removeItem(R.id.action_sort_order); menu.removeItem(R.id.action_sort_order);
ToolbarContentTintHelper.handleOnCreateOptionsMenu( ToolbarContentTintHelper.handleOnCreateOptionsMenu(
requireContext(), toolbar, menu, getToolbarBackgroundColor(toolbar)); requireContext(), binding.toolbar, menu, getToolbarBackgroundColor(binding.toolbar));
} }
@Override @Override
@ -462,27 +480,33 @@ public class FoldersFragment extends AbsMainActivityFragment
private void checkForPadding() { private void checkForPadding() {
final int count = adapter.getItemCount(); final int count = adapter.getItemCount();
final MarginLayoutParams params = (MarginLayoutParams) coordinatorLayout.getLayoutParams(); if (binding != null) {
final MarginLayoutParams params = (MarginLayoutParams) binding.coordinatorLayout.getLayoutParams();
params.bottomMargin = params.bottomMargin =
count > 0 && !MusicPlayerRemote.getPlayingQueue().isEmpty() count > 0 && !MusicPlayerRemote.getPlayingQueue().isEmpty()
? DensityUtil.dip2px(requireContext(), 104f) ? DensityUtil.dip2px(requireContext(), 104f)
: DensityUtil.dip2px(requireContext(), 54f); : DensityUtil.dip2px(requireContext(), 54f);
binding.coordinatorLayout.setLayoutParams(params);
}
} }
private void checkIsEmpty() { private void checkIsEmpty() {
emojiText.setText(getEmojiByUnicode(0x1F631)); if (binding != null) {
if (empty != null) { binding.emptyEmoji.setText(getEmojiByUnicode(0x1F631));
empty.setVisibility( binding.empty.setVisibility(
adapter == null || adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); adapter == null || adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
} }
} }
@Nullable @Nullable
private BreadCrumbLayout.Crumb getActiveCrumb() { private BreadCrumbLayout.Crumb getActiveCrumb() {
return breadCrumbs != null && breadCrumbs.size() > 0 if (binding != null) {
? breadCrumbs.getCrumb(breadCrumbs.getActiveIndex()) return binding.breadCrumbs.size() > 0
? binding.breadCrumbs.getCrumb(binding.breadCrumbs.getActiveIndex())
: null; : null;
} }
return null;
}
private String getEmojiByUnicode(int unicode) { private String getEmojiByUnicode(int unicode) {
return new String(Character.toChars(unicode)); return new String(Character.toChars(unicode));
@ -492,21 +516,11 @@ public class FoldersFragment extends AbsMainActivityFragment
return fileComparator; return fileComparator;
} }
private void initViews(View view) {
coordinatorLayout = view.findViewById(R.id.coordinatorLayout);
recyclerView = view.findViewById(R.id.recyclerView);
breadCrumbs = view.findViewById(R.id.breadCrumbs);
empty = view.findViewById(android.R.id.empty);
emojiText = view.findViewById(R.id.emptyEmoji);
toolbar = view.findViewById(R.id.toolbar);
appNameText = view.findViewById(R.id.appNameText);
}
private void saveScrollPosition() { private void saveScrollPosition() {
BreadCrumbLayout.Crumb crumb = getActiveCrumb(); BreadCrumbLayout.Crumb crumb = getActiveCrumb();
if (crumb != null) { if (crumb != null) {
crumb.setScrollPosition( crumb.setScrollPosition(
((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition()); ((LinearLayoutManager) binding.recyclerView.getLayoutManager()).findFirstVisibleItemPosition());
} }
} }
@ -529,46 +543,39 @@ public class FoldersFragment extends AbsMainActivityFragment
if (crumb == null) { if (crumb == null) {
return; return;
} }
String path = crumb.getFile().getPath();
if (path.equals("/") || path.equals("/storage") || path.equals("/storage/emulated")) {
switchToStorageAdapter();
} else {
saveScrollPosition(); saveScrollPosition();
breadCrumbs.setActiveOrAdd(crumb, false); binding.breadCrumbs.setActiveOrAdd(crumb, false);
if (addToHistory) { if (addToHistory) {
breadCrumbs.addHistory(crumb); binding.breadCrumbs.addHistory(crumb);
} }
LoaderManager.getInstance(this).restartLoader(LOADER_ID, null, this); LoaderManager.getInstance(this).restartLoader(LOADER_ID, null, this);
} }
}
private void setUpAdapter() { private void setUpAdapter() {
adapter = switchToFileAdapter();
new SongFileAdapter(getMainActivity(), new LinkedList<>(), R.layout.item_list, this, this);
adapter.registerAdapterDataObserver(
new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
super.onChanged();
checkIsEmpty();
checkForPadding();
}
});
recyclerView.setAdapter(adapter);
checkIsEmpty();
} }
private void setUpAppbarColor() { private void setUpAppbarColor() {
breadCrumbs.setActivatedContentColor( binding.breadCrumbs.setActivatedContentColor(
ATHUtil.INSTANCE.resolveColor(requireContext(), android.R.attr.textColorPrimary)); ATHUtil.INSTANCE.resolveColor(requireContext(), android.R.attr.textColorPrimary));
breadCrumbs.setDeactivatedContentColor( binding.breadCrumbs.setDeactivatedContentColor(
ATHUtil.INSTANCE.resolveColor(requireContext(), android.R.attr.textColorSecondary)); ATHUtil.INSTANCE.resolveColor(requireContext(), android.R.attr.textColorSecondary));
} }
private void setUpBreadCrumbs() { private void setUpBreadCrumbs() {
breadCrumbs.setCallback(this); binding.breadCrumbs.setCallback(this);
} }
private void setUpRecyclerView() { private void setUpRecyclerView() {
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); binding.recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
FastScroller fastScroller = ThemedFastScroller.INSTANCE.create(recyclerView); FastScroller fastScroller = ThemedFastScroller.INSTANCE.create(binding.recyclerView);
recyclerView.setOnApplyWindowInsetsListener( binding.recyclerView.setOnApplyWindowInsetsListener(
new ScrollingViewOnApplyWindowInsetsListener(recyclerView, fastScroller)); new ScrollingViewOnApplyWindowInsetsListener(binding.recyclerView, fastScroller));
} }
private ArrayList<File> toList(File file) { private ArrayList<File> toList(File file) {
@ -580,16 +587,22 @@ public class FoldersFragment extends AbsMainActivityFragment
private void updateAdapter(@NonNull List<File> files) { private void updateAdapter(@NonNull List<File> files) {
adapter.swapDataSet(files); adapter.swapDataSet(files);
BreadCrumbLayout.Crumb crumb = getActiveCrumb(); BreadCrumbLayout.Crumb crumb = getActiveCrumb();
if (crumb != null && recyclerView != null) { if (crumb != null) {
((LinearLayoutManager) recyclerView.getLayoutManager()) ((LinearLayoutManager) binding.recyclerView.getLayoutManager())
.scrollToPositionWithOffset(crumb.getScrollPosition(), 0); .scrollToPositionWithOffset(crumb.getScrollPosition(), 0);
} }
} }
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
public static class ListPathsAsyncTask public static class ListPathsAsyncTask
extends ListingFilesDialogAsyncTask<ListPathsAsyncTask.LoadingInfo, String, String[]> { extends ListingFilesDialogAsyncTask<ListPathsAsyncTask.LoadingInfo, String, String[]> {
private WeakReference<OnPathsListedCallback> onPathsListedCallbackWeakReference; private final WeakReference<OnPathsListedCallback> onPathsListedCallbackWeakReference;
public ListPathsAsyncTask(Context context, OnPathsListedCallback callback) { public ListPathsAsyncTask(Context context, OnPathsListedCallback callback) {
super(context); super(context);
@ -679,7 +692,7 @@ public class FoldersFragment extends AbsMainActivityFragment
private static class AsyncFileLoader extends WrappedAsyncTaskLoader<List<File>> { private static class AsyncFileLoader extends WrappedAsyncTaskLoader<List<File>> {
private WeakReference<FoldersFragment> fragmentWeakReference; private final WeakReference<FoldersFragment> fragmentWeakReference;
AsyncFileLoader(FoldersFragment foldersFragment) { AsyncFileLoader(FoldersFragment foldersFragment) {
super(foldersFragment.requireActivity()); super(foldersFragment.requireActivity());
@ -710,8 +723,8 @@ public class FoldersFragment extends AbsMainActivityFragment
extends ListingFilesDialogAsyncTask<ListSongsAsyncTask.LoadingInfo, Void, List<Song>> { extends ListingFilesDialogAsyncTask<ListSongsAsyncTask.LoadingInfo, Void, List<Song>> {
private final Object extra; private final Object extra;
private WeakReference<OnSongsListedCallback> callbackWeakReference; private final WeakReference<OnSongsListedCallback> callbackWeakReference;
private WeakReference<Context> contextWeakReference; private final WeakReference<Context> contextWeakReference;
ListSongsAsyncTask(Context context, Object extra, OnSongsListedCallback callback) { ListSongsAsyncTask(Context context, Object extra, OnSongsListedCallback callback) {
super(context); super(context);
@ -823,4 +836,107 @@ public class FoldersFragment extends AbsMainActivityFragment
.create(); .create();
} }
} }
// https://github.com/DrKLO/Telegram/blob/ab221dafadbc17459d78d9ea3e643ae18e934b16/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertDocumentLayout.java#L939
private void listRoots() {
storageItems.clear();
HashSet<String> paths = new HashSet<>();
String defaultPath = Environment.getExternalStorageDirectory().getPath();
String defaultPathState = Environment.getExternalStorageState();
if (defaultPathState.equals(Environment.MEDIA_MOUNTED) || defaultPathState.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
Storage ext = new Storage();
if (Environment.isExternalStorageRemovable()) {
ext.title = "SD Card";
} else {
ext.title = "Internal Storage";
}
ext.file = Environment.getExternalStorageDirectory();
storageItems.add(ext);
paths.add(defaultPath);
}
BufferedReader bufferedReader = null;
try {
bufferedReader = new BufferedReader(new FileReader("/proc/mounts"));
String line;
while ((line = bufferedReader.readLine()) != null) {
if (line.contains("vfat") || line.contains("/mnt")) {
StringTokenizer tokens = new StringTokenizer(line, " ");
tokens.nextToken();
String path = tokens.nextToken();
if (paths.contains(path)) {
continue;
}
if (line.contains("/dev/block/vold")) {
if (!line.contains("/mnt/secure") && !line.contains("/mnt/asec") && !line.contains("/mnt/obb") && !line.contains("/dev/mapper") && !line.contains("tmpfs")) {
if (!new File(path).isDirectory()) {
int index = path.lastIndexOf('/');
if (index != -1) {
String newPath = "/storage/" + path.substring(index + 1);
if (new File(newPath).isDirectory()) {
path = newPath;
}
}
}
paths.add(path);
try {
Storage item = new Storage();
if (path.toLowerCase().contains("sd")) {
item.title = "SD Card";
} else {
item.title = "External Storage";
}
item.file = new File(path);
storageItems.add(item);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
@Override
public void onStorageClicked(@NonNull Storage storage) {
switchToFileAdapter();
setCrumb(
new BreadCrumbLayout.Crumb(
FileUtil.safeGetCanonicalFile(storage.file)),
true);
}
public void switchToFileAdapter() {
adapter =
new SongFileAdapter(getMainActivity(), new LinkedList<>(), R.layout.item_list, this, this);
adapter.registerAdapterDataObserver(
new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
super.onChanged();
checkIsEmpty();
checkForPadding();
}
});
binding.recyclerView.setAdapter(adapter);
checkIsEmpty();
}
public void switchToStorageAdapter() {
listRoots();
storageAdapter = new StorageAdapter(storageItems, this);
binding.recyclerView.setAdapter(storageAdapter);
binding.breadCrumbs.clearCrumbs();
}
} }

View file

@ -14,28 +14,26 @@
*/ */
package code.name.monkey.retromusic.fragments.genres package code.name.monkey.retromusic.fragments.genres
import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.doOnPreDraw
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.song.SongAdapter import code.name.monkey.retromusic.adapter.song.SongAdapter
import code.name.monkey.retromusic.databinding.FragmentPlaylistDetailBinding
import code.name.monkey.retromusic.extensions.dipToPix import code.name.monkey.retromusic.extensions.dipToPix
import code.name.monkey.retromusic.extensions.resolveColor
import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import code.name.monkey.retromusic.helper.menu.GenreMenuHelper import code.name.monkey.retromusic.helper.menu.GenreMenuHelper
import code.name.monkey.retromusic.model.Genre import code.name.monkey.retromusic.model.Genre
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import com.google.android.material.transition.MaterialArcMotion import com.google.android.material.transition.MaterialSharedAxis
import com.google.android.material.transition.MaterialContainerTransform
import kotlinx.android.synthetic.main.fragment_playlist_detail.*
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
import java.util.* import java.util.*
@ -47,37 +45,33 @@ class GenreDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playlist_
} }
private lateinit var genre: Genre private lateinit var genre: Genre
private lateinit var songAdapter: SongAdapter private lateinit var songAdapter: SongAdapter
private var _binding: FragmentPlaylistDetailBinding? = null
override fun onCreate(savedInstanceState: Bundle?) { private val binding get() = _binding!!
super.onCreate(savedInstanceState)
sharedElementEnterTransition = MaterialContainerTransform().apply {
drawingViewId = R.id.fragment_container
duration = 300L
scrimColor = Color.TRANSPARENT
setAllContainerColors(requireContext().resolveColor(R.attr.colorSurface))
setPathMotion(MaterialArcMotion())
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true).addTarget(view)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
_binding = FragmentPlaylistDetailBinding.bind(view)
setHasOptionsMenu(true) setHasOptionsMenu(true)
mainActivity.setBottomBarVisibility(false)
mainActivity.addMusicServiceEventListener(detailsViewModel) mainActivity.addMusicServiceEventListener(detailsViewModel)
mainActivity.setSupportActionBar(toolbar) mainActivity.setSupportActionBar(binding.toolbar)
ViewCompat.setTransitionName(container, "genre") ViewCompat.setTransitionName(binding.container, "genre")
genre = arguments.extraGenre genre = arguments.extraGenre
toolbar?.title = arguments.extraGenre.name binding.toolbar.title = arguments.extraGenre.name
setupRecyclerView() setupRecyclerView()
detailsViewModel.getSongs().observe(viewLifecycleOwner, { detailsViewModel.getSongs().observe(viewLifecycleOwner, {
songs(it) songs(it)
}) })
postponeEnterTransition()
view.doOnPreDraw {
startPostponedEnterTransition()
}
} }
private fun setupRecyclerView() { private fun setupRecyclerView() {
songAdapter = SongAdapter(requireActivity(), ArrayList(), R.layout.item_list, null) songAdapter = SongAdapter(requireActivity(), ArrayList(), R.layout.item_list, null)
recyclerView.apply { binding.recyclerView.apply {
itemAnimator = DefaultItemAnimator() itemAnimator = DefaultItemAnimator()
layoutManager = LinearLayoutManager(requireContext()) layoutManager = LinearLayoutManager(requireContext())
adapter = songAdapter adapter = songAdapter
@ -91,7 +85,7 @@ class GenreDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playlist_
} }
fun songs(songs: List<Song>) { fun songs(songs: List<Song>) {
progressIndicator.hide() binding.progressIndicator.hide()
if (songs.isNotEmpty()) songAdapter.swapDataSet(songs) if (songs.isNotEmpty()) songAdapter.swapDataSet(songs)
else songAdapter.swapDataSet(emptyList()) else songAdapter.swapDataSet(emptyList())
} }
@ -102,13 +96,13 @@ class GenreDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playlist_
private fun checkIsEmpty() { private fun checkIsEmpty() {
checkForPadding() checkForPadding()
emptyEmoji.text = getEmojiByUnicode(0x1F631) binding.emptyEmoji.text = getEmojiByUnicode(0x1F631)
empty?.visibility = if (songAdapter.itemCount == 0) View.VISIBLE else View.GONE binding.empty.visibility = if (songAdapter.itemCount == 0) View.VISIBLE else View.GONE
} }
private fun checkForPadding() { private fun checkForPadding() {
val height = dipToPix(52f).toInt() val height = dipToPix(52f).toInt()
recyclerView.setPadding(0, 0, 0, height) binding.recyclerView.setPadding(0, 0, 0, height)
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
@ -119,4 +113,9 @@ class GenreDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playlist_
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
return GenreMenuHelper.handleMenuClick(requireActivity(), genre, item) return GenreMenuHelper.handleMenuClick(requireActivity(), genre, item)
} }
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
} }

View file

@ -60,4 +60,5 @@ class GenreDetailsViewModel(
override fun onPlayStateChanged() {} override fun onPlayStateChanged() {}
override fun onRepeatModeChanged() {} override fun onRepeatModeChanged() {}
override fun onShuffleModeChanged() {} override fun onShuffleModeChanged() {}
override fun onFavoriteStateChanged() {}
} }

View file

@ -15,25 +15,31 @@
package code.name.monkey.retromusic.fragments.genres package code.name.monkey.retromusic.fragments.genres
import android.os.Bundle import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View import android.view.View
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.lifecycle.Observer
import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import code.name.monkey.retromusic.EXTRA_GENRE import code.name.monkey.retromusic.EXTRA_GENRE
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.GenreAdapter import code.name.monkey.retromusic.adapter.GenreAdapter
import code.name.monkey.retromusic.fragments.ReloadType
import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewFragment import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewFragment
import code.name.monkey.retromusic.interfaces.IGenreClickListener import code.name.monkey.retromusic.interfaces.IGenreClickListener
import code.name.monkey.retromusic.model.Genre import code.name.monkey.retromusic.model.Genre
import com.google.android.material.transition.MaterialElevationScale import code.name.monkey.retromusic.util.RetroUtil
import com.google.android.gms.cast.framework.CastButtonFactory
import com.google.android.material.transition.MaterialSharedAxis
class GenresFragment : AbsRecyclerViewFragment<GenreAdapter, LinearLayoutManager>(), class
GenresFragment : AbsRecyclerViewFragment<GenreAdapter, LinearLayoutManager>(),
IGenreClickListener { IGenreClickListener {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
libraryViewModel.getGenre().observe(viewLifecycleOwner, Observer { libraryViewModel.getGenre().observe(viewLifecycleOwner, {
if (it.isNotEmpty()) if (it.isNotEmpty())
adapter?.swapDataSet(it) adapter?.swapDataSet(it)
else else
@ -42,14 +48,37 @@ class GenresFragment : AbsRecyclerViewFragment<GenreAdapter, LinearLayoutManager
} }
override fun createLayoutManager(): LinearLayoutManager { override fun createLayoutManager(): LinearLayoutManager {
return LinearLayoutManager(activity) return if (RetroUtil.isLandscape()) {
GridLayoutManager(activity, 4)
} else {
GridLayoutManager(activity, 2)
}
} }
override fun createAdapter(): GenreAdapter { override fun createAdapter(): GenreAdapter {
val dataSet = if (adapter == null) ArrayList() else adapter!!.dataSet val dataSet = if (adapter == null) ArrayList() else adapter!!.dataSet
return GenreAdapter(requireActivity(), dataSet, R.layout.item_list_no_image, this) return GenreAdapter(requireActivity(), dataSet, R.layout.item_genre, this)
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
menu.removeItem(R.id.action_grid_size)
menu.removeItem(R.id.action_layout_type)
menu.removeItem(R.id.action_sort_order)
menu.findItem(R.id.action_settings).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
//Setting up cast button
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.action_cast)
}
override fun onResume() {
super.onResume()
libraryViewModel.forceReload(ReloadType.Genres)
}
override val titleRes: Int
get() = R.string.genres
override val emptyMessage: Int override val emptyMessage: Int
get() = R.string.no_genres get() = R.string.no_genres
@ -64,19 +93,13 @@ class GenresFragment : AbsRecyclerViewFragment<GenreAdapter, LinearLayoutManager
} }
override fun onClickGenre(genre: Genre, view: View) { override fun onClickGenre(genre: Genre, view: View) {
exitTransition = MaterialElevationScale(false).apply { exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true).addTarget(requireView())
duration = 300L reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
}
reenterTransition = MaterialElevationScale(true).apply {
duration = 300L
}
findNavController().navigate( findNavController().navigate(
R.id.genreDetailsFragment, R.id.genreDetailsFragment,
bundleOf(EXTRA_GENRE to genre), bundleOf(EXTRA_GENRE to genre),
null, null,
FragmentNavigatorExtras( null
view to "genre"
)
) )
} }
} }

Some files were not shown because too many files have changed in this diff Show more