Merge pull request #1191 from prathameshmm02/dev

Added New features and bug fixes
This commit is contained in:
Daksh P. Jain 2021-12-24 22:45:48 +05:30 committed by GitHub
commit c988f2f0fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
183 changed files with 3591 additions and 1574 deletions

View file

@ -5,18 +5,17 @@ apply plugin: "androidx.navigation.safeargs.kotlin"
apply plugin: 'kotlin-parcelize' apply plugin: 'kotlin-parcelize'
android { android {
compileSdkVersion 31 compileSdk 31
defaultConfig { defaultConfig {
minSdkVersion 21 minSdk 21
targetSdkVersion 31 targetSdk 31
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 10545 versionCode 10551
versionName '5.4.2 ' + "_" + getDate() versionName '5.5.0'
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')}\"")
} }
@ -31,9 +30,10 @@ android {
} }
buildTypes { buildTypes {
release { release {
//debuggable true versionNameSuffix "_" + getDate()
shrinkResources true
minifyEnabled true minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release signingConfig signingConfigs.release
} }
debug { debug {
@ -95,22 +95,21 @@ dependencies {
implementation 'androidx.annotation:annotation:1.3.0' implementation 'androidx.annotation:annotation:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.2' implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
implementation 'androidx.recyclerview:recyclerview:1.3.0-alpha01' implementation 'androidx.recyclerview:recyclerview:1.3.0-alpha01'
implementation 'androidx.preference:preference-ktx:1.2.0-beta01' implementation "androidx.preference:preference-ktx:$preference_version"
implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.palette:palette-ktx:1.0.0' implementation 'androidx.palette:palette-ktx:1.0.0'
//Cast Dependencies //Cast Dependencies
implementation 'androidx.mediarouter:mediarouter:1.2.5' implementation 'androidx.mediarouter:mediarouter:1.2.5'
implementation 'com.google.android.gms:play-services-cast-framework:20.1.0' implementation 'com.google.android.gms:play-services-cast-framework:21.0.0'
//WebServer by NanoHttpd //WebServer by NanoHttpd
implementation "org.nanohttpd:nanohttpd:2.3.1" implementation "org.nanohttpd:nanohttpd:2.3.1"
def nav_version = '2.4.0-beta02' implementation "androidx.navigation:navigation-runtime-ktx:$navigation_version"
implementation "androidx.navigation:navigation-runtime-ktx:$nav_version" implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
def room_version = '2.4.0-rc01' def room_version = '2.4.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"
@ -121,7 +120,7 @@ dependencies {
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.5.0-beta01' implementation "com.google.android.material:material:$mdc_version"
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"
@ -138,7 +137,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
def kotlin_coroutines_version = '1.6.0-RC' def kotlin_coroutines_version = '1.6.0-RC3'
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"
@ -152,15 +151,19 @@ dependencies {
implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0' implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0'
implementation 'com.github.bosphere.android-fadingedgelayout:fadingedgelayout:1.0.0'
implementation 'net.yslibrary.keyboardvisibilityevent:keyboardvisibilityevent:3.0.0-RC3'
implementation 'com.github.jetradarmobile:android-snowfall:1.2.1'
implementation 'org.eclipse.mylyn.github:org.eclipse.egit.github.core:2.1.5' implementation 'org.eclipse.mylyn.github:org.eclipse.egit.github.core:2.1.5'
implementation 'com.github.AdrienPoupa:jaudiotagger:2.2.3' implementation 'com.github.Adonai:jaudiotagger:2.3.15'
implementation 'com.anjlab.android.iab.v3:library:2.0.3' implementation 'com.anjlab.android.iab.v3:library:2.0.3'
implementation 'com.r0adkll:slidableactivity:2.1.0' implementation 'com.r0adkll:slidableactivity:2.1.0'
implementation 'com.heinrichreimersoftware:material-intro:2.0.0' implementation 'com.heinrichreimersoftware:material-intro:2.0.0'
implementation 'com.github.dhaval2404:imagepicker:2.1' implementation 'com.github.dhaval2404:imagepicker:2.1'
implementation 'me.zhanghai.android.fastscroll:library:1.1.7' implementation 'me.zhanghai.android.fastscroll:library:1.1.7'
implementation 'cat.ereza:customactivityoncrash:2.3.0' implementation 'cat.ereza:customactivityoncrash:2.3.0'
implementation 'me.tankery.lib:circularSeekBar:1.3.2'
debugImplementation 'com.github.amitshekhariitbhu:Android-Debug-Database:1.0.6' debugImplementation 'com.github.amitshekhariitbhu:Android-Debug-Database:1.0.6'
} }
apply from: '../spotless.gradle'

View file

@ -49,8 +49,11 @@
#-dontwarn #-dontwarn
#-ignorewarnings #-ignorewarnings
#Jaudiotagger
-dontwarn org.jaudiotagger.** -dontwarn org.jaudiotagger.**
-dontwarn org.jcodec.**
-keep class org.jaudiotagger.** { *; } -keep class org.jaudiotagger.** { *; }
-keep class org.jcodec.** { *; }
-keepclassmembers enum * { *; } -keepclassmembers enum * { *; }
-keepattributes *Annotation*, Signature, Exception -keepattributes *Annotation*, Signature, Exception

View file

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<bool name="md3_available">true</bool> <bool name="md3_available">true</bool>
<bool name="allowBackup">false</bool>
</resources> </resources>

View file

@ -94,8 +94,7 @@
<item name="android:textColor">?android:attr/textColorPrimary</item> <item name="android:textColor">?android:attr/textColorPrimary</item>
</style> </style>
<style name="circleImageView" parent=""> <style name="circleImageView" parent="ShapeAppearance.MaterialComponents">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">40dp</item> <item name="cornerSize">40dp</item>
</style> </style>

View file

@ -22,11 +22,12 @@
<application <application
android:name=".App" android:name=".App"
android:allowBackup="true" android:allowBackup="@bool/allowBackup"
android:configChanges="locale|layoutDirection" android:configChanges="locale|layoutDirection"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:requestLegacyExternalStorage="true" android:requestLegacyExternalStorage="true"
android:restoreAnyVersion="true"
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"
@ -122,15 +123,38 @@
<activity android:name=".activities.LockScreenActivity" /> <activity android:name=".activities.LockScreenActivity" />
<activity <activity
android:name=".fragments.backup.RestoreActivity" android:name=".fragments.backup.RestoreActivity"
android:exported="true"> android:excludeFromRecents="false"
android:exported="true"
android:label="@string/restore"
android:theme="@style/Theme.RetroMusic.Dialog">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:mimeType="application/octet-stream" />
<data android:mimeType="application/x-zip-compressed" />
<data android:mimeType="application/zip" />
<data android:scheme="file" /> <data android:scheme="file" />
<data android:scheme="content" />
<data android:mimeType="*/*" /> <data android:mimeType="*/*" />
<!--
Work around Android's ugly primitive PatternMatcher
implementation that can't cope with finding a . early in
the path unless it's explicitly matched.
-->
<data android:host="*" />
<data android:pathPattern=".*\\.rmbak" /> <data android:pathPattern=".*\\.rmbak" />
<data android:pathPattern=".*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.rmbak" />
</intent-filter> </intent-filter>
</activity> </activity>
@ -272,10 +296,9 @@
<service <service
android:name=".service.MusicService" android:name=".service.MusicService"
android:enabled="true" android:enabled="true"
android:exported="true" android:exported="false"
android:foregroundServiceType="mediaPlayback" android:foregroundServiceType="mediaPlayback"
android:label="@string/app_name" android:label="@string/app_name">
tools:ignore="ExportedService">
<intent-filter> <intent-filter>
<action android:name="android.media.browse.MediaBrowserService" /> <action android:name="android.media.browse.MediaBrowserService" />
</intent-filter> </intent-filter>
@ -298,4 +321,12 @@
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME" android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="code.name.monkey.retromusic.cast.CastOptionsProvider" /> android:value="code.name.monkey.retromusic.cast.CastOptionsProvider" />
</application> </application>
<!--
This is not that important, it's just here so that we can query equalizer package
and check if it's present on A11+ because of Package visibility restrictions.
-->
<queries>
<package android:name="com.android.musicfx" />
</queries>
</manifest> </manifest>

View file

@ -24,40 +24,54 @@
padding-top: 8px; padding-top: 8px;
} }
</style> </style>
</head> </head>
<body> <body>
<p><b><a href="https://github.com/kabouzeid/Phonograph" title="Phonograph"> Phonograph</a></b> by <p><b><a href="https://github.com/kabouzeid/Phonograph" title="Phonograph"> Phonograph</a></b> by
Karim Abou Zeid</p> Karim Abou Zeid</p>
<p><b><a href="https://github.com/afollestad" title="Material Dialogs"> Material Dialogs and Cab</a></b>
by Aidan Michael Follestad</p>
<p><b><a href="http://developer.android.com/tools/support-library/index.html" <p><b><a href="http://developer.android.com/tools/support-library/index.html"
title="AOSP Support Libraries"> AOSP Support Libraries</a></b>by AOSP contributors</p> title="AOSP Support Libraries">AOSP Support Libraries</a></b> by AOSP contributors</p>
<p><b><a href="https://github.com/bumptech/glide" title="Glide"> Glide</a></b> by Sam Judd</p> <p><b><a href="https://github.com/bumptech/glide" title="Glide"> Glide</a></b> by Sam Judd</p>
<p><b><a href="https://github.com/square/retrofit" title="Retrofit"> Retrofit</a></b> by Square team <p><b><a href="https://github.com/square/retrofit" title="Retrofit"> Retrofit</a></b> by Square team
</p> </p>
<p><b><a href="http://square.github.io/okhttp/" title="OkHttp"> OkHttp</a></b> by Square team</p>
<p><b><a href="https://github.com/InsertKoinIO/koin"
title="Koin">Koin</a></b> by Arnaud Giuliani</p>
<p><b><a href="https://github.com/afollestad" title="Material Dialogs"> Material Dialogs and Cab</a></b>
by Aidan Michael Follestad</p>
<p><b><a href="https://github.com/afollestad/material-cab" title="Material Contextual Action Bar"> <p><b><a href="https://github.com/afollestad/material-cab" title="Material Contextual Action Bar">
Material Contextual Action Bar</a></b> by Aidan Michael Follestad</p> Material Contextual Action Bar</a></b> by Aidan Michael Follestad</p>
<p><b><a href="http://square.github.io/okhttp/" title="OkHttp"> OkHttp</a></b> by Square team</p>
<p><b><a href="https://github.com/hdodenhof/CircleImageView" title="CircleImageView">
CircleImageView</a></b> by Henning Dodenhof</p>
<p><b><a href="https://github.com/DreaminginCodeZH/MaterialProgressBar" title="MaterialProgressBar">
MaterialProgressBar</a></b> by Zhang Hai</p>
<p><b><a href="https://github.com/anjlab/android-inapp-billing-v3" <p><b><a href="https://github.com/anjlab/android-inapp-billing-v3"
title="Android In-App Billing v3 Library"> Android In-App Billing v3 Library</a></b> by title="Android In-App Billing v3 Library"> Android In-App Billing v3 Library</a></b> by
Henning Dodenhof</p> Henning Dodenhof</p>
<p><b><a href="https://github.com/h6ah4i/android-advancedrecyclerview" <p><b><a href="https://github.com/h6ah4i/android-advancedrecyclerview"
title="Advanced RecyclerView"> Advanced RecyclerView</a></b> by Haruki Hasegawa</p> title="Advanced RecyclerView"> Advanced RecyclerView</a></b> by Haruki Hasegawa</p>
<p><b><a href="https://github.com/ksoichiro/Android-ObservableScrollView" <p><b><a href="https://github.com/Ereza/CustomActivityOnCrash"
title="Android-ObservableScrollView"> Android-ObservableScrollView</a></b> by Soichiro title="Custom Activity on Crash">Custom Activity on Crash</a></b> by Eduard Ereza Martínez
Kashima</p> </p>
<p><b><a href="https://github.com/NanoHttpd/nanohttpd"
title="NanoHttpd">NanoHttpd</a></b> by NanoHttpd Team</p>
<p><b><a href="https://github.com/tankery/CircularSeekBar"
title="Circular Seekbar">Circular Seekbar</a></b> by Tankery</p>
<p><b><a href="https://github.com/Kaned1as/jaudiotagger"
title="jAudioTagger">jAudioTagger</a></b> by Kanedias</p>
<p><b><a href="https://github.com/zhanghai/AndroidFastScroll"
title="Android Fast Scroll">Android Fast Scroll</a></b> by Zhang Hai</p>
<p><b><a href="https://github.com/Dhaval2404/ImagePicker"
title="Image Picker">Image Picker</a></b> by Dhaval Patel</p>
<p><b><a href="https://github.com/heinrichreimer/material-intro"
title="Material Intro">Material Intro</a></b> by Jan Heinrich Reimer</p>
<p><b><a href="https://github.com/r0adkll/Slidr"
title="Slidr">Slidr</a></b> by Drew Heavner</p>
<p><b><a href="https://github.com/bosphere/Android-FadingEdgeLayout"
title="FadingEdgeLayout">FadingEdgeLayout</a></b> by bosphere</p>
<p><b><a href="https://github.com/yshrsmz/KeyboardVisibilityEvent"
title="KeyboardVisibilityEvent">KeyboardVisibilityEvent</a></b> by Yasuhiro SHIMIZU</p>
<p><b><a href="https://github.com/JetradarMobile/android-snowfall"
title="android-snowfall">android-snowfall</a></b> by Jetradar Mobile</p>
<p><b><a href="https://materialdesignicons.com" title="Icons"> Icons</a></b> by Austin Andrews</p> <p><b><a href="https://materialdesignicons.com" title="Icons"> Icons</a></b> by Austin Andrews</p>
<p><b><a href="https://www.techjuice.pk" title="City wallpaper"> Material Design City Wallpaper</a></b> <p><b><a href="https://www.techjuice.pk" title="City wallpaper"> Material Design City Wallpaper</a></b>
</p> </p>
</body> </body>
</html> </html>

View file

@ -63,7 +63,75 @@
<body> <body>
<div> <div>
<h5>Date</h5> <h5>December 24, 2021</h5>
<h2>v5.5.0<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3>
<ul>
<li>Added Artwork editing for songs</li>
<li>Circle theme has album art now</li>
<li>Upgraded tag editor library, we should support reading tags of more formats now e.g.
opus.
</li>
<li>Added Snowfall effect</li>
<li>Crossfade effect for background when Song is changed for Blur, Blur card themes</li>
<li>Album art is hidden when Show lyrics is enabled</li>
<li>Added fastscroll in Playlists tab</li>
<li>Added Chooser to choose what to restore</li>
<li>Hide BottomNavigation when only one tab is selected in Library Categories(This was
already there but when changing screens it was getting visible)
</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed Ringtone crash</li>
<li>Fixed blank album cover bug</li>
<li>Fixed bottom navigation visible in Playing Queue</li>
<li>Fixed lockscreen dragging glitch</li>
<li>Fixed incorrect colors when no cover art is available</li>
<li>Fixed favorite not updating when song is changed</li>
<li>Fixed playlist not getting created & playlist creation crash with same name</li>
<li>Fixed a bug in "Plain" Now playing theme where onClick event is consumed by the views
behind the bottom sheet
</li>
</ul>
</div>
<div>
<h5>December 6, 2021</h5>
<h2>v5.4.2<span class="tag"><i>Beta</i></span></h2>
<h3>Fixed</h3>
<ul>
<li>Bug Fixes</li>
</ul>
</div>
<div>
<h5>December 1, 2021</h5>
<h2>v5.4.1<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3>
<ul>
<li>Added file selection from system file picker for restore</li>
<li>Added Grid size selection for Playlists</li>
</ul>
<h3>Improved</h3>
<ul>
<li>Enable Material You by default on Android 12</li>
<li>Reworked Grid Style saving</li>
<li>Improved Playlist preview images</li>
<li>UI improvements</li>
</ul>
<h3>Improved</h3>
<ul>
<li>Fixed File deletion on Android 10</li>
<li>Fixed Sleep Timer crash</li>
<li>Fixed Bottom Toolbar not clickable in now playing when Shuffle is clicked</li>
<li>Fixed Album Artist sort order</li>
<li>Fixed a crash when clicking the "Clear All" button in the Blacklist folder selection
</li>
<li>Fixed Continuous crashes on A12 when the song ends</li>
<li>Fixed New Music Mix multiple clicks crash</li>
</ul>
</div>
<div>
<h5>November 22, 2021</h5>
<h2>v5.4.0<span class="tag"><i>Beta</i></span></h2> <h2>v5.4.0<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3> <h3>What's New</h3>
<ul> <ul>

View file

@ -152,3 +152,4 @@ const val REMEMBER_LAST_TAB = "remember_last_tab"
const val LAST_USED_TAB = "last_used_tab" const val LAST_USED_TAB = "last_used_tab"
const val WHITELIST_MUSIC = "whitelist_music" const val WHITELIST_MUSIC = "whitelist_music"
const val MATERIAL_YOU = "material_you" const val MATERIAL_YOU = "material_you"
const val SNOWFALL = "snowfall"

View file

@ -15,16 +15,19 @@
package code.name.monkey.retromusic.activities package code.name.monkey.retromusic.activities
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.os.Bundle import android.os.Bundle
import android.view.animation.LinearInterpolator import android.view.animation.LinearInterpolator
import android.widget.SeekBar import android.widget.SeekBar
import androidx.lifecycle.lifecycleScope
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.databinding.ActivityDriveModeBinding
import code.name.monkey.retromusic.extensions.setDrawUnderStatusBar import code.name.monkey.retromusic.db.toSongEntity
import code.name.monkey.retromusic.extensions.drawAboveSystemBars
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.GlideApp
@ -35,13 +38,15 @@ import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper.Callback import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper.Callback
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.repository.RealRepository
import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.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 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 org.koin.android.ext.android.inject
/** /**
@ -54,9 +59,9 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
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
private val repository: RealRepository by inject()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityDriveModeBinding.inflate(layoutInflater) binding = ActivityDriveModeBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
@ -67,6 +72,7 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
binding.close.setOnClickListener { binding.close.setOnClickListener {
onBackPressed() onBackPressed()
} }
binding.repeatButton.drawAboveSystemBars()
} }
private fun setUpMusicControllers() { private fun setUpMusicControllers() {
@ -80,19 +86,32 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
private fun setupFavouriteToggle() { private fun setupFavouriteToggle() {
binding.songFavourite.setOnClickListener { binding.songFavourite.setOnClickListener {
MusicUtil.toggleFavorite( toggleFavorite(MusicPlayerRemote.currentSong)
this@DriveModeActivity,
MusicPlayerRemote.currentSong
)
} }
} }
private fun toggleFavourite() { private fun toggleFavorite(song: Song) {
CoroutineScope(Dispatchers.IO).launch { lifecycleScope.launch(Dispatchers.IO) {
val isFavourite = val playlist = repository.favoritePlaylist()
MusicUtil.isFavorite(this@DriveModeActivity, MusicPlayerRemote.currentSong) if (playlist != null) {
val songEntity = song.toSongEntity(playlist.playListId)
val isFavorite = repository.isSongFavorite(song.id)
if (isFavorite) {
repository.removeSongFromPlaylist(songEntity)
} else {
repository.insertSongs(listOf(song.toSongEntity(playlist.playListId)))
}
}
sendBroadcast(Intent(MusicService.FAVORITE_STATE_CHANGED))
}
}
private fun updateFavorite() {
lifecycleScope.launch(Dispatchers.IO) {
val isFavorite: Boolean =
repository.isSongFavorite(MusicPlayerRemote.currentSong.id)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
binding.songFavourite.setImageResource(if (isFavourite) R.drawable.ic_favorite else R.drawable.ic_favorite_border) binding.songFavourite.setImageResource(if (isFavorite) R.drawable.ic_favorite else R.drawable.ic_favorite_border)
} }
} }
} }
@ -160,7 +179,7 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
updateSong() updateSong()
updateRepeatState() updateRepeatState()
updateShuffleState() updateShuffleState()
toggleFavourite() updateFavorite()
} }
private fun updatePlayPauseDrawableState() { private fun updatePlayPauseDrawableState() {
@ -213,7 +232,12 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
override fun onPlayingMetaChanged() { override fun onPlayingMetaChanged() {
super.onPlayingMetaChanged() super.onPlayingMetaChanged()
updateSong() updateSong()
toggleFavourite() updateFavorite()
}
override fun onFavoriteStateChanged() {
super.onFavoriteStateChanged()
updateFavorite()
} }
private fun updateSong() { private fun updateSong() {

View file

@ -16,47 +16,41 @@ package code.name.monkey.retromusic.activities
import android.graphics.Color 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 androidx.appcompat.widget.Toolbar
import code.name.monkey.appthemehelper.ThemeStore.Companion.accentColor import code.name.monkey.appthemehelper.ThemeStore.Companion.accentColor
import code.name.monkey.appthemehelper.util.ATHUtil.isWindowBackgroundDark import code.name.monkey.appthemehelper.util.ATHUtil.isWindowBackgroundDark
import code.name.monkey.appthemehelper.util.ATHUtil.resolveColor
import code.name.monkey.appthemehelper.util.ColorUtil.lightenColor import code.name.monkey.appthemehelper.util.ColorUtil.lightenColor
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.activities.base.AbsThemeActivity import code.name.monkey.retromusic.activities.base.AbsThemeActivity
import code.name.monkey.retromusic.databinding.ActivityLicenseBinding
import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.extensions.surfaceColor
import java.io.BufferedReader import java.io.BufferedReader
import java.io.InputStreamReader import java.io.InputStreamReader
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
/** Created by hemanths on 2019-09-27. */ /** Created by hemanths on 2019-09-27. */
class LicenseActivity : AbsThemeActivity() { class LicenseActivity : AbsThemeActivity() {
private lateinit var binding: ActivityLicenseBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_license) binding = ActivityLicenseBinding.inflate(layoutInflater)
val toolbar = findViewById<Toolbar>(R.id.toolbar) setContentView(binding.root)
setSupportActionBar(toolbar) setSupportActionBar(binding.toolbar)
ToolbarContentTintHelper.colorBackButton(toolbar) ToolbarContentTintHelper.colorBackButton(binding.toolbar)
toolbar.setBackgroundColor(resolveColor(this, R.attr.colorSurface))
val webView = findViewById<WebView>(R.id.license)
try { try {
val buf = StringBuilder() val buf = StringBuilder()
val json = assets.open("oldindex.html") val json = assets.open("license.html")
val br = BufferedReader(InputStreamReader(json, StandardCharsets.UTF_8)) BufferedReader(InputStreamReader(json, StandardCharsets.UTF_8)).use { br ->
var str: String? var str: String?
while (br.readLine().also { str = it } != null) { while (br.readLine().also { str = it } != null) {
buf.append(str) buf.append(str)
} }
br.close() }
// Inject color values for WebView body background and links // Inject color values for WebView body background and links
val isDark = isWindowBackgroundDark(this) val isDark = isWindowBackgroundDark(this)
val backgroundColor = colorToCSS( val backgroundColor = colorToCSS(
resolveColor( surfaceColor(Color.parseColor(if (isDark) "#424242" else "#ffffff"))
this,
R.attr.colorSurface,
Color.parseColor(if (isDark) "#424242" else "#ffffff")
)
) )
val contentColor = colorToCSS(Color.parseColor(if (isDark) "#ffffff" else "#000000")) val contentColor = colorToCSS(Color.parseColor(if (isDark) "#ffffff" else "#000000"))
val changeLog = buf.toString() val changeLog = buf.toString()
@ -72,12 +66,13 @@ class LicenseActivity : AbsThemeActivity() {
lightenColor(accentColor(this)) lightenColor(accentColor(this))
) )
) )
webView.loadData(changeLog, "text/html", "UTF-8") binding.license.loadData(changeLog, "text/html", "UTF-8")
} catch (e: Throwable) { } catch (e: Throwable) {
webView.loadData( binding.license.loadData(
"<h1>Unable to load</h1><p>" + e.localizedMessage + "</p>", "text/html", "UTF-8" "<h1>Unable to load</h1><p>" + e.localizedMessage + "</p>", "text/html", "UTF-8"
) )
} }
binding.license.drawAboveSystemBars()
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {

View file

@ -18,9 +18,9 @@ import android.app.KeyguardManager
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.View
import android.view.WindowManager import android.view.WindowManager
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
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.AbsMusicServiceActivity import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.databinding.ActivityLockScreenBinding import code.name.monkey.retromusic.databinding.ActivityLockScreenBinding
@ -41,13 +41,11 @@ class LockScreenActivity : AbsMusicServiceActivity() {
private var fragment: LockScreenControlsFragment? = null private var fragment: LockScreenControlsFragment? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
lockScreenInit() lockScreenInit()
binding = ActivityLockScreenBinding.inflate(layoutInflater) binding = ActivityLockScreenBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
hideStatusBar() hideStatusBar()
setStatusBarColorAuto()
setTaskDescriptionColorAuto() setTaskDescriptionColorAuto()
val config = SlidrConfig.Builder().listener(object : SlidrListener { val config = SlidrConfig.Builder().listener(object : SlidrListener {
@ -61,7 +59,7 @@ class LockScreenActivity : AbsMusicServiceActivity() {
} }
override fun onSlideClosed(): Boolean { override fun onSlideClosed(): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (VersionUtils.hasOreo()) {
val keyguardManager = val keyguardManager =
getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
keyguardManager.requestDismissKeyguard(this@LockScreenActivity, null) keyguardManager.requestDismissKeyguard(this@LockScreenActivity, null)
@ -75,7 +73,7 @@ class LockScreenActivity : AbsMusicServiceActivity() {
fragment = whichFragment<LockScreenControlsFragment>(R.id.playback_controls_fragment) fragment = whichFragment<LockScreenControlsFragment>(R.id.playback_controls_fragment)
findViewById<View>(R.id.slide).apply { binding.slide.apply {
translationY = 100f translationY = 100f
alpha = 0f alpha = 0f
ViewCompat.animate(this).translationY(0f).alpha(1f).setDuration(1500).start() ViewCompat.animate(this).translationY(0f).alpha(1f).setDuration(1500).start()

View file

@ -27,15 +27,15 @@ import code.name.monkey.retromusic.*
import code.name.monkey.retromusic.activities.base.AbsCastActivity import code.name.monkey.retromusic.activities.base.AbsCastActivity
import code.name.monkey.retromusic.databinding.SlidingMusicPanelLayoutBinding 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.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.interfaces.IScrollHelper
import code.name.monkey.retromusic.model.CategoryInfo import code.name.monkey.retromusic.model.CategoryInfo
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.repository.PlaylistSongsLoader import code.name.monkey.retromusic.repository.PlaylistSongsLoader
import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.AppRater import code.name.monkey.retromusic.util.AppRater
import code.name.monkey.retromusic.util.NavigationUtil
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.launch import kotlinx.coroutines.launch
@ -52,7 +52,6 @@ class MainActivity : AbsCastActivity(), OnSharedPreferenceChangeListener {
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setTaskDescriptionColorAuto() setTaskDescriptionColorAuto()
hideStatusBar() hideStatusBar()
@ -63,6 +62,9 @@ class MainActivity : AbsCastActivity(), OnSharedPreferenceChangeListener {
if (!hasPermissions()) { if (!hasPermissions()) {
findNavController(R.id.fragment_container).navigate(R.id.permissionFragment) findNavController(R.id.fragment_container).navigate(R.id.permissionFragment)
} }
if (BuildConfig.VERSION_CODE > PreferenceUtil.lastVersion){
NavigationUtil.gotoWhatNews(this)
}
} }
private fun setupNavigationController() { private fun setupNavigationController() {
@ -90,20 +92,16 @@ class MainActivity : AbsCastActivity(), OnSharedPreferenceChangeListener {
bottomNavigationView.setupWithNavController(navController) bottomNavigationView.setupWithNavController(navController)
// Scroll Fragment to top // Scroll Fragment to top
bottomNavigationView.setOnItemReselectedListener { bottomNavigationView.setOnItemReselectedListener {
currentFragment(R.id.fragment_container) currentFragment(R.id.fragment_container).apply {
.also { if (this is IScrollHelper) {
if (it is AbsRecyclerViewFragment<*, *>) { scrollToTop()
it.scrollToTop()
}
if (it is HomeFragment) {
it.scrollToTop()
} }
} }
} }
// This is more like a work-around as for start destination of navGraph
// enterTransition won't work as expected
navGraph.setStartDestination(R.id.libraryFragment)
navController.addOnDestinationChangedListener { _, destination, _ -> navController.addOnDestinationChangedListener { _, destination, _ ->
if (destination.id == navGraph.startDestinationId) {
currentFragment(R.id.fragment_container)?.enterTransition = null
}
when (destination.id) { when (destination.id) {
R.id.action_home, R.id.action_song, R.id.action_album, R.id.action_artist, R.id.action_folder, R.id.action_playlist, R.id.action_genre -> { R.id.action_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 // Save the last tab
@ -114,10 +112,12 @@ class MainActivity : AbsCastActivity(), OnSharedPreferenceChangeListener {
setBottomNavVisibility(visible = true, animate = true) setBottomNavVisibility(visible = true, animate = true)
} }
R.id.playing_queue_fragment -> { R.id.playing_queue_fragment -> {
setBottomNavVisibility(visible = false) setBottomNavVisibility(visible = false, hideBottomSheet = true)
hideBottomSheet(true)
} }
else -> setBottomNavVisibility(visible = false, animate = true) // Hide Bottom Navigation Bar else -> setBottomNavVisibility(
visible = false,
animate = true
) // Hide Bottom Navigation Bar
} }
} }
} }

View file

@ -22,9 +22,9 @@ import android.net.Uri
import android.os.Build 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.annotation.RequiresApi
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import androidx.core.view.isVisible
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.activities.base.AbsMusicServiceActivity import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
@ -84,12 +84,12 @@ class PermissionActivity : AbsMusicServiceActivity() {
@RequiresApi(Build.VERSION_CODES.M) @RequiresApi(Build.VERSION_CODES.M)
override fun onResume() { override fun onResume() {
if (hasStoragePermission()) { if (hasStoragePermission()) {
binding.storagePermission.checkImage.visibility = View.VISIBLE binding.storagePermission.checkImage.isVisible = true
binding.storagePermission.checkImage.imageTintList = binding.storagePermission.checkImage.imageTintList =
ColorStateList.valueOf(ThemeStore.accentColor(this)) ColorStateList.valueOf(ThemeStore.accentColor(this))
} }
if (hasAudioPermission()) { if (hasAudioPermission()) {
binding.audioPermission.checkImage.visibility = View.VISIBLE binding.audioPermission.checkImage.isVisible = true
binding.audioPermission.checkImage.imageTintList = binding.audioPermission.checkImage.imageTintList =
ColorStateList.valueOf(ThemeStore.accentColor(this)) ColorStateList.valueOf(ThemeStore.accentColor(this))
} }

View file

@ -28,7 +28,6 @@ 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 code.name.monkey.retromusic.databinding.ActivityProVersionBinding
import code.name.monkey.retromusic.extensions.setDrawUnderStatusBar
import code.name.monkey.retromusic.extensions.setLightStatusBar import code.name.monkey.retromusic.extensions.setLightStatusBar
import code.name.monkey.retromusic.extensions.setStatusBarColor import code.name.monkey.retromusic.extensions.setStatusBarColor
import com.anjlab.android.iab.v3.BillingProcessor import com.anjlab.android.iab.v3.BillingProcessor
@ -40,7 +39,6 @@ class PurchaseActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler {
private lateinit var billingProcessor: BillingProcessor private lateinit var billingProcessor: BillingProcessor
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityProVersionBinding.inflate(layoutInflater) binding = ActivityProVersionBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)

View file

@ -32,15 +32,18 @@ import com.afollestad.materialdialogs.color.ColorCallback
class SettingsActivity : AbsThemeActivity(), ColorCallback, OnThemeChangedListener { class SettingsActivity : AbsThemeActivity(), ColorCallback, OnThemeChangedListener {
private lateinit var binding: ActivitySettingsBinding private lateinit var binding: ActivitySettingsBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
val mSavedInstanceState = extra<Bundle>(TAG).value ?: savedInstanceState val mSavedInstanceState = extra<Bundle>(TAG).value ?: savedInstanceState
super.onCreate(mSavedInstanceState) super.onCreate(mSavedInstanceState)
setLightStatusBarAuto(surfaceColor())
binding = ActivitySettingsBinding.inflate(layoutInflater) binding = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
setupToolbar() setupToolbar()
} }
override fun onResume() {
super.onResume()
setNavigationBarColorPreOreo(surfaceColor())
}
private fun setupToolbar() { private fun setupToolbar() {
applyToolbar(binding.toolbar) applyToolbar(binding.toolbar)
val navController: NavController = findNavController(R.id.contentFrame) val navController: NavController = findNavController(R.id.contentFrame)
@ -82,7 +85,6 @@ class SettingsActivity : AbsThemeActivity(), ColorCallback, OnThemeChangedListen
ThemeStore.editTheme(this).accentColor(color).commit() ThemeStore.editTheme(this).accentColor(color).commit()
if (VersionUtils.hasNougatMR()) if (VersionUtils.hasNougatMR())
DynamicShortcutManager(this).updateDynamicShortcuts() DynamicShortcutManager(this).updateDynamicShortcuts()
restart() restart()
} }

View file

@ -28,8 +28,6 @@ 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.activities.base.AbsBaseActivity import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.databinding.ActivityShareInstagramBinding import code.name.monkey.retromusic.databinding.ActivityShareInstagramBinding
import code.name.monkey.retromusic.extensions.applyToolbar
import code.name.monkey.retromusic.extensions.setDrawUnderStatusBar
import code.name.monkey.retromusic.extensions.setLightStatusBar import code.name.monkey.retromusic.extensions.setLightStatusBar
import code.name.monkey.retromusic.extensions.setStatusBarColor import code.name.monkey.retromusic.extensions.setStatusBarColor
import code.name.monkey.retromusic.glide.GlideApp import code.name.monkey.retromusic.glide.GlideApp
@ -60,7 +58,6 @@ class ShareInstagramStory : AbsBaseActivity() {
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityShareInstagramBinding.inflate(layoutInflater) binding = ActivityShareInstagramBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)

View file

@ -37,10 +37,7 @@ 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.databinding.ActivityDonationBinding
import code.name.monkey.retromusic.extensions.setStatusBarColorAuto import code.name.monkey.retromusic.extensions.*
import code.name.monkey.retromusic.extensions.setTaskDescriptionColorAuto
import code.name.monkey.retromusic.extensions.textColorPrimary
import code.name.monkey.retromusic.extensions.textColorSecondary
import com.anjlab.android.iab.v3.BillingProcessor import com.anjlab.android.iab.v3.BillingProcessor
import com.anjlab.android.iab.v3.PurchaseInfo import com.anjlab.android.iab.v3.PurchaseInfo
import com.anjlab.android.iab.v3.SkuDetails import com.anjlab.android.iab.v3.SkuDetails
@ -86,7 +83,7 @@ class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingH
} }
private fun setupToolbar() { private fun setupToolbar() {
val toolbarColor = ATHUtil.resolveColor(this, R.attr.colorSurface) val toolbarColor = surfaceColor()
binding.toolbar.setBackgroundColor(toolbarColor) binding.toolbar.setBackgroundColor(toolbarColor)
ToolbarContentTintHelper.colorBackButton(binding.toolbar) ToolbarContentTintHelper.colorBackButton(binding.toolbar)
setSupportActionBar(binding.toolbar) setSupportActionBar(binding.toolbar)

View file

@ -7,18 +7,14 @@ import android.os.Bundle
import androidx.core.widget.NestedScrollView import androidx.core.widget.NestedScrollView
import code.name.monkey.appthemehelper.ThemeStore.Companion.accentColor import code.name.monkey.appthemehelper.ThemeStore.Companion.accentColor
import code.name.monkey.appthemehelper.util.ATHUtil.isWindowBackgroundDark import code.name.monkey.appthemehelper.util.ATHUtil.isWindowBackgroundDark
import code.name.monkey.appthemehelper.util.ATHUtil.resolveColor
import code.name.monkey.appthemehelper.util.ColorUtil.isColorLight import code.name.monkey.appthemehelper.util.ColorUtil.isColorLight
import code.name.monkey.appthemehelper.util.ColorUtil.lightenColor import code.name.monkey.appthemehelper.util.ColorUtil.lightenColor
import code.name.monkey.appthemehelper.util.MaterialValueHelper.getPrimaryTextColor import code.name.monkey.appthemehelper.util.MaterialValueHelper.getPrimaryTextColor
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.Constants import code.name.monkey.retromusic.Constants
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsThemeActivity import code.name.monkey.retromusic.activities.base.AbsThemeActivity
import code.name.monkey.retromusic.databinding.ActivityWhatsNewBinding import code.name.monkey.retromusic.databinding.ActivityWhatsNewBinding
import code.name.monkey.retromusic.extensions.accentColor import code.name.monkey.retromusic.extensions.*
import code.name.monkey.retromusic.extensions.setLightStatusBarAuto
import code.name.monkey.retromusic.extensions.setTaskDescriptionColorAuto
import code.name.monkey.retromusic.util.PreferenceUtil.lastVersion import code.name.monkey.retromusic.util.PreferenceUtil.lastVersion
import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil
import java.io.BufferedReader import java.io.BufferedReader
@ -31,29 +27,25 @@ class WhatsNewActivity : AbsThemeActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val binding = ActivityWhatsNewBinding.inflate(layoutInflater) val binding = ActivityWhatsNewBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
setLightStatusBarAuto(resolveColor(this, R.attr.colorSurface)) setLightStatusBarAuto(surfaceColor())
setTaskDescriptionColorAuto() setTaskDescriptionColorAuto()
binding.toolbar.setNavigationOnClickListener { onBackPressed() } binding.toolbar.setNavigationOnClickListener { onBackPressed() }
ToolbarContentTintHelper.colorBackButton(binding.toolbar) ToolbarContentTintHelper.colorBackButton(binding.toolbar)
try { try {
val buf = StringBuilder() val buf = StringBuilder()
val json = assets.open("retro-changelog.html") val json = assets.open("retro-changelog.html")
val br = BufferedReader(InputStreamReader(json, StandardCharsets.UTF_8)) BufferedReader(InputStreamReader(json, StandardCharsets.UTF_8)).use { br ->
var str: String? var str: String?
while (br.readLine().also { str = it } != null) { while (br.readLine().also { str = it } != null) {
buf.append(str) buf.append(str)
} }
br.close() }
// Inject color values for WebView body background and links // Inject color values for WebView body background and links
val isDark = isWindowBackgroundDark(this) val isDark = isWindowBackgroundDark(this)
val accentColor = accentColor(this) val accentColor = accentColor(this)
val backgroundColor = colorToCSS( val backgroundColor = colorToCSS(
resolveColor( surfaceColor(Color.parseColor(if (isDark) "#424242" else "#ffffff"))
this,
R.attr.colorSurface,
Color.parseColor(if (isDark) "#424242" else "#ffffff")
)
) )
val contentColor = colorToCSS(Color.parseColor(if (isDark) "#ffffff" else "#000000")) val contentColor = colorToCSS(Color.parseColor(if (isDark) "#ffffff" else "#000000"))
val textColor = colorToCSS(Color.parseColor(if (isDark) "#60FFFFFF" else "#80000000")) val textColor = colorToCSS(Color.parseColor(if (isDark) "#60FFFFFF" else "#80000000"))
@ -100,6 +92,7 @@ class WhatsNewActivity : AbsThemeActivity() {
binding.tgFab.extend() binding.tgFab.extend()
} }
} }
binding.webView.drawAboveSystemBars()
} }
companion object { companion object {

View file

@ -14,21 +14,20 @@
*/ */
package code.name.monkey.retromusic.activities.base package code.name.monkey.retromusic.activities.base
import android.animation.ArgbEvaluator
import android.animation.ValueAnimator
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.Color import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewTreeObserver import android.view.ViewTreeObserver
import android.view.animation.PathInterpolator
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.core.animation.doOnEnd import androidx.core.animation.doOnEnd
import androidx.core.view.ViewCompat import androidx.core.view.*
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.commit import androidx.fragment.app.commit
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
@ -61,9 +60,11 @@ import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.model.CategoryInfo 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.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil
import code.name.monkey.retromusic.util.ViewUtil
import com.google.android.material.bottomsheet.BottomSheetBehavior.* import com.google.android.material.bottomsheet.BottomSheetBehavior.*
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
@ -78,21 +79,34 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
private var nowPlayingScreen: NowPlayingScreen? = null private var nowPlayingScreen: NowPlayingScreen? = null
private var taskColor: Int = 0 private var taskColor: Int = 0
private var paletteColor: Int = Color.WHITE private var paletteColor: Int = Color.WHITE
private var navigationBarColor = 0
protected abstract fun createContentView(): SlidingMusicPanelLayoutBinding 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 lateinit var binding: SlidingMusicPanelLayoutBinding
private var isInOneTabMode = false
private var navigationBarColorAnimator: ValueAnimator? = null
private val argbEvaluator: ArgbEvaluator = ArgbEvaluator()
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)
navigationBarColorAnimator?.cancel()
setNavigationBarColorPreOreo(
argbEvaluator.evaluate(
slideOffset,
surfaceColor(),
navigationBarColor
) as Int
)
} }
override fun onStateChanged(bottomSheet: View, newState: Int) { override fun onStateChanged(bottomSheet: View, newState: Int) {
when (newState) { when (newState) {
STATE_EXPANDED -> { STATE_EXPANDED -> {
onPanelExpanded() onPanelExpanded()
} }
STATE_COLLAPSED -> { STATE_COLLAPSED -> {
onPanelCollapsed() onPanelCollapsed()
@ -122,7 +136,6 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
windowInsets = insets windowInsets = insets
insets insets
} }
bottomNavigationView.drawAboveSystemBarsWithPadding()
if (RetroUtil.isLandscape()) { if (RetroUtil.isLandscape()) {
binding.slidingPanel.drawAboveSystemBarsWithPadding(true) binding.slidingPanel.drawAboveSystemBarsWithPadding(true)
} }
@ -132,6 +145,7 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
updateColor() updateColor()
binding.slidingPanel.backgroundTintList = ColorStateList.valueOf(darkAccentColor()) binding.slidingPanel.backgroundTintList = ColorStateList.valueOf(darkAccentColor())
bottomNavigationView.backgroundTintList = ColorStateList.valueOf(darkAccentColor()) bottomNavigationView.backgroundTintList = ColorStateList.valueOf(darkAccentColor())
navigationBarColor = surfaceColor()
} }
private fun setupBottomSheet() { private fun setupBottomSheet() {
@ -178,9 +192,25 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
binding.playerFragmentContainer.alpha = (progress - 0.2F) / 0.2F binding.playerFragmentContainer.alpha = (progress - 0.2F) / 0.2F
} }
private fun animateNavigationBarColor(color: Int) {
navigationBarColorAnimator?.cancel()
navigationBarColorAnimator = ValueAnimator
.ofArgb(window.navigationBarColor, color).apply {
duration = ViewUtil.RETRO_MUSIC_ANIM_TIME.toLong()
interpolator = PathInterpolator(0.4f, 0f, 1f, 1f)
addUpdateListener { animation: ValueAnimator ->
setNavigationBarColorPreOreo(
animation.animatedValue as Int
)
}
start()
}
}
open fun onPanelCollapsed() { open fun onPanelCollapsed() {
setMiniPlayerAlphaProgress(0F) setMiniPlayerAlphaProgress(0F)
// restore values // restore values
animateNavigationBarColor(surfaceColor())
setLightStatusBarAuto(surfaceColor()) setLightStatusBarAuto(surfaceColor())
setLightNavigationAuto() setLightNavigationAuto()
setTaskDescriptionColor(taskColor) setTaskDescriptionColor(taskColor)
@ -233,9 +263,7 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
super.onQueueChanged() super.onQueueChanged()
// Mini player should be hidden in Playing Queue // Mini player should be hidden in Playing Queue
// it may pop up if hideBottomSheet is called // it may pop up if hideBottomSheet is called
if (currentFragment(R.id.fragment_container) !is PlayingQueueFragment && if (currentFragment(R.id.fragment_container) !is PlayingQueueFragment) {
bottomSheetBehavior.state != STATE_EXPANDED
) {
hideBottomSheet(MusicPlayerRemote.playingQueue.isEmpty()) hideBottomSheet(MusicPlayerRemote.playingQueue.isEmpty())
} }
} }
@ -255,18 +283,25 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
private fun onPaletteColorChanged() { private fun onPaletteColorChanged() {
if (panelState == STATE_EXPANDED) { if (panelState == STATE_EXPANDED) {
navigationBarColor = surfaceColor()
setTaskDescColor(paletteColor) setTaskDescColor(paletteColor)
val isColorLight = ColorUtil.isColorLight(paletteColor) val isColorLight = ColorUtil.isColorLight(paletteColor)
if (PreferenceUtil.isAdaptiveColor && (nowPlayingScreen == Normal || nowPlayingScreen == Flat)) { if (PreferenceUtil.isAdaptiveColor && (nowPlayingScreen == Normal || nowPlayingScreen == Flat)) {
setLightNavigationBar(true) setLightNavigationBar(true)
setLightStatusBar(isColorLight) setLightStatusBar(isColorLight)
} else if (nowPlayingScreen == Card || nowPlayingScreen == Blur || nowPlayingScreen == BlurCard) { } else if (nowPlayingScreen == Card || nowPlayingScreen == Blur || nowPlayingScreen == BlurCard) {
animateNavigationBarColor(Color.BLACK)
navigationBarColor = Color.BLACK
setLightStatusBar(false) setLightStatusBar(false)
setLightNavigationBar(true) setLightNavigationBar(true)
} else if (nowPlayingScreen == Color || nowPlayingScreen == Tiny || nowPlayingScreen == Gradient) { } else if (nowPlayingScreen == Color || nowPlayingScreen == Tiny || nowPlayingScreen == Gradient) {
animateNavigationBarColor(paletteColor)
navigationBarColor = paletteColor
setLightNavigationBar(isColorLight) setLightNavigationBar(isColorLight)
setLightStatusBar(isColorLight) setLightStatusBar(isColorLight)
} else if (nowPlayingScreen == Full) { } else if (nowPlayingScreen == Full) {
animateNavigationBarColor(paletteColor)
navigationBarColor = paletteColor
setLightNavigationBar(isColorLight) setLightNavigationBar(isColorLight)
setLightStatusBar(false) setLightStatusBar(false)
} else if (nowPlayingScreen == Classic) { } else if (nowPlayingScreen == Classic) {
@ -276,10 +311,7 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
} else { } else {
setLightStatusBar( setLightStatusBar(
ColorUtil.isColorLight( ColorUtil.isColorLight(
ATHUtil.resolveColor( surfaceColor()
this,
android.R.attr.windowBackground
)
) )
) )
setLightNavigationBar(true) setLightNavigationBar(true)
@ -305,6 +337,7 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
} }
} }
if (binding.bottomNavigationView.menu.size() == 1) { if (binding.bottomNavigationView.menu.size() == 1) {
isInOneTabMode = true
binding.bottomNavigationView.hide() binding.bottomNavigationView.hide()
} }
} }
@ -316,10 +349,19 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
}) })
} }
fun setBottomNavVisibility(visible: Boolean, animate: Boolean = false) { fun setBottomNavVisibility(visible: Boolean, animate: Boolean = false, hideBottomSheet: Boolean = MusicPlayerRemote.playingQueue.isEmpty()) {
if (isInOneTabMode) {
hideBottomSheet(
hide = hideBottomSheet,
animate = animate,
isBottomNavVisible = false
)
return
}
val translationY = val translationY =
if (visible) 0F else dip(R.dimen.bottom_nav_height).toFloat() + windowInsets.safeGetBottomInsets() if (visible) 0F else dip(R.dimen.bottom_nav_height).toFloat() + windowInsets.safeGetBottomInsets()
if (animate) { val mAnimate = animate && bottomSheetBehavior.state == STATE_COLLAPSED
if (mAnimate) {
binding.bottomNavigationView.translateYAnimate(translationY).doOnEnd { binding.bottomNavigationView.translateYAnimate(translationY).doOnEnd {
if (visible && bottomSheetBehavior.state != STATE_EXPANDED) { if (visible && bottomSheetBehavior.state != STATE_EXPANDED) {
binding.bottomNavigationView.bringToFront() binding.bottomNavigationView.bringToFront()
@ -328,12 +370,13 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
} else { } else {
binding.bottomNavigationView.translationY = binding.bottomNavigationView.translationY =
translationY translationY
binding.bottomNavigationView.isVisible = false
if (visible && bottomSheetBehavior.state != STATE_EXPANDED) { if (visible && bottomSheetBehavior.state != STATE_EXPANDED) {
binding.bottomNavigationView.bringToFront() binding.bottomNavigationView.bringToFront()
} }
} }
hideBottomSheet( hideBottomSheet(
hide = MusicPlayerRemote.playingQueue.isEmpty(), hide = hideBottomSheet,
animate = animate, animate = animate,
isBottomNavVisible = visible isBottomNavVisible = visible
) )

View file

@ -35,6 +35,7 @@ abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable {
private val handler = Handler() private val handler = Handler()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawBehindSystemBars()
updateTheme() updateTheme()
hideStatusBar() hideStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

View file

@ -34,7 +34,6 @@ import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding 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.TintHelper import code.name.monkey.appthemehelper.util.TintHelper
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.R
@ -60,14 +59,13 @@ import java.io.File
import java.util.* import java.util.*
abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() { abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
abstract val editorImage: ImageView? abstract val editorImage: ImageView
val repository by inject<Repository>() val repository by inject<Repository>()
lateinit var saveFab: MaterialButton lateinit var saveFab: MaterialButton
protected var id: Long = 0 protected var id: Long = 0
private set private set
private var paletteColorPrimary: Int = 0 private var paletteColorPrimary: Int = 0
private var isInNoImageMode: Boolean = false
private var songPaths: List<String>? = null private var songPaths: List<String>? = null
private var savedSongPaths: List<String>? = null private var savedSongPaths: List<String>? = null
private val currentSongPath: String? = null private val currentSongPath: String? = null
@ -176,6 +174,15 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
} }
} }
protected val discNumber: String?
get() {
return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.DISC_NO)
} catch (ignored: Exception) {
null
}
}
protected val lyrics: String? protected val lyrics: String?
get() { get() {
return try { return try {
@ -239,7 +246,7 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
getString(R.string.web_search), getString(R.string.web_search),
getString(R.string.remove_cover) getString(R.string.remove_cover)
) )
editorImage?.setOnClickListener { show } editorImage.setOnClickListener { show }
} }
private fun startImagePicker() { private fun startImagePicker() {
@ -306,17 +313,6 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
protected fun setNoImageMode() {
isInNoImageMode = true
setColors(
intent.getIntExtra(
EXTRA_PALETTE,
ATHUtil.resolveColor(this, R.attr.colorPrimary)
)
)
}
protected fun dataChanged() { protected fun dataChanged() {
showFab() showFab()
} }
@ -335,9 +331,9 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : 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

@ -28,11 +28,12 @@ import android.transition.Slide
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.ImageView 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.MaterialValueHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.ActivityAlbumTagEditorBinding 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.extensions.setDrawUnderStatusBar import code.name.monkey.retromusic.extensions.defaultFooterColor
import code.name.monkey.retromusic.extensions.isColorLight
import code.name.monkey.retromusic.extensions.setTint import code.name.monkey.retromusic.extensions.setTint
import code.name.monkey.retromusic.glide.GlideApp import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
@ -64,41 +65,6 @@ class AlbumTagEditorActivity : AbsTagEditorActivity<ActivityAlbumTagEditorBindin
window.enterTransition = slide window.enterTransition = slide
} }
override fun loadImageFromFile(selectedFile: Uri?) {
GlideApp.with(this@AlbumTagEditorActivity).asBitmapPalette().load(selectedFile)
.diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true)
.into(object : ImageViewTarget<BitmapPaletteWrapper>(binding.editorImage) {
override fun onResourceReady(
resource: BitmapPaletteWrapper,
transition: Transition<in BitmapPaletteWrapper>?
) {
getColor(resource.palette, Color.TRANSPARENT)
albumArtBitmap = resource.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) }
setImageBitmap(
albumArtBitmap,
getColor(
resource.palette,
ATHUtil.resolveColor(
this@AlbumTagEditorActivity,
R.attr.defaultFooterColor
)
)
)
deleteAlbumArt = false
dataChanged()
setResult(Activity.RESULT_OK)
}
override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(errorDrawable)
Toast.makeText(this@AlbumTagEditorActivity, "Load Failed", Toast.LENGTH_LONG)
.show()
}
override fun setResource(resource: BitmapPaletteWrapper?) {}
})
}
private var albumArtBitmap: Bitmap? = null private var albumArtBitmap: Bitmap? = null
private var deleteAlbumArt: Boolean = false private var deleteAlbumArt: Boolean = false
@ -109,7 +75,6 @@ class AlbumTagEditorActivity : AbsTagEditorActivity<ActivityAlbumTagEditorBindin
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
window.sharedElementsUseOverlay = true window.sharedElementsUseOverlay = true
binding.imageContainer.transitionName = getString(R.string.transition_album_art) binding.imageContainer.transitionName = getString(R.string.transition_album_art)
@ -146,7 +111,7 @@ class AlbumTagEditorActivity : AbsTagEditorActivity<ActivityAlbumTagEditorBindin
bitmap, bitmap,
getColor( getColor(
generatePalette(bitmap), generatePalette(bitmap),
ATHUtil.resolveColor(this, R.attr.defaultFooterColor) defaultFooterColor()
) )
) )
deleteAlbumArt = false deleteAlbumArt = false
@ -167,12 +132,44 @@ class AlbumTagEditorActivity : AbsTagEditorActivity<ActivityAlbumTagEditorBindin
override fun deleteImage() { override fun deleteImage() {
setImageBitmap( setImageBitmap(
BitmapFactory.decodeResource(resources, R.drawable.default_audio_art), BitmapFactory.decodeResource(resources, R.drawable.default_audio_art),
ATHUtil.resolveColor(this, R.attr.defaultFooterColor) defaultFooterColor()
) )
deleteAlbumArt = true deleteAlbumArt = true
dataChanged() dataChanged()
} }
override fun loadImageFromFile(selectedFile: Uri?) {
GlideApp.with(this@AlbumTagEditorActivity).asBitmapPalette().load(selectedFile)
.diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true)
.into(object : ImageViewTarget<BitmapPaletteWrapper>(binding.editorImage) {
override fun onResourceReady(
resource: BitmapPaletteWrapper,
transition: Transition<in BitmapPaletteWrapper>?
) {
getColor(resource.palette, Color.TRANSPARENT)
albumArtBitmap = resource.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) }
setImageBitmap(
albumArtBitmap,
getColor(
resource.palette,
defaultFooterColor()
)
)
deleteAlbumArt = false
dataChanged()
setResult(Activity.RESULT_OK)
}
override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(errorDrawable)
Toast.makeText(this@AlbumTagEditorActivity, "Load Failed", Toast.LENGTH_LONG)
.show()
}
override fun setResource(resource: BitmapPaletteWrapper?) {}
})
}
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] = binding.albumText.text.toString() fieldKeyValueMap[FieldKey.ALBUM] = binding.albumText.text.toString()
@ -215,6 +212,16 @@ class AlbumTagEditorActivity : AbsTagEditorActivity<ActivityAlbumTagEditorBindin
override fun setColors(color: Int) { override fun setColors(color: Int) {
super.setColors(color) super.setColors(color)
saveFab.backgroundTintList = ColorStateList.valueOf(color) saveFab.backgroundTintList = ColorStateList.valueOf(color)
saveFab.backgroundTintList = ColorStateList.valueOf(color)
ColorStateList.valueOf(
MaterialValueHelper.getPrimaryTextColor(
this,
color.isColorLight
)
).also {
saveFab.iconTint = it
saveFab.setTextColor(it)
}
} }

View file

@ -15,17 +15,36 @@
package code.name.monkey.retromusic.activities.tageditor package code.name.monkey.retromusic.activities.tageditor
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity
import android.content.res.ColorStateList
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.net.Uri import android.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.view.LayoutInflater
import android.widget.ImageView import android.widget.ImageView
import android.widget.Toast
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.ActivitySongTagEditorBinding import code.name.monkey.retromusic.databinding.ActivitySongTagEditorBinding
import code.name.monkey.retromusic.extensions.appHandleColor import code.name.monkey.retromusic.extensions.appHandleColor
import code.name.monkey.retromusic.extensions.defaultFooterColor
import code.name.monkey.retromusic.extensions.isColorLight
import code.name.monkey.retromusic.extensions.setTint import code.name.monkey.retromusic.extensions.setTint
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
import code.name.monkey.retromusic.model.ArtworkInfo
import code.name.monkey.retromusic.repository.SongRepository import code.name.monkey.retromusic.repository.SongRepository
import code.name.monkey.retromusic.util.ImageUtil
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.RetroColorUtil
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.target.ImageViewTarget
import com.bumptech.glide.request.transition.Transition
import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.MaterialShapeDrawable
import org.jaudiotagger.tag.FieldKey import org.jaudiotagger.tag.FieldKey
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
@ -39,12 +58,14 @@ class SongTagEditorActivity : AbsTagEditorActivity<ActivitySongTagEditorBinding>
private val songRepository by inject<SongRepository>() private val songRepository by inject<SongRepository>()
private var albumArtBitmap: Bitmap? = null
private var deleteAlbumArt: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setUpViews() setUpViews()
setNoImageMode()
setSupportActionBar(binding.toolbar) setSupportActionBar(binding.toolbar)
binding.appBarLayout.statusBarForeground = binding.appBarLayout?.statusBarForeground =
MaterialShapeDrawable.createWithElevationOverlay(this) MaterialShapeDrawable.createWithElevationOverlay(this)
} }
@ -59,6 +80,7 @@ class SongTagEditorActivity : AbsTagEditorActivity<ActivitySongTagEditorBinding>
binding.yearContainer.setTint(false) binding.yearContainer.setTint(false)
binding.genreContainer.setTint(false) binding.genreContainer.setTint(false)
binding.trackNumberContainer.setTint(false) binding.trackNumberContainer.setTint(false)
binding.discNumberContainer.setTint(false)
binding.lyricsContainer.setTint(false) binding.lyricsContainer.setTint(false)
binding.songText.appHandleColor().addTextChangedListener(this) binding.songText.appHandleColor().addTextChangedListener(this)
@ -68,13 +90,9 @@ class SongTagEditorActivity : AbsTagEditorActivity<ActivitySongTagEditorBinding>
binding.genreText.appHandleColor().addTextChangedListener(this) binding.genreText.appHandleColor().addTextChangedListener(this)
binding.yearText.appHandleColor().addTextChangedListener(this) binding.yearText.appHandleColor().addTextChangedListener(this)
binding.trackNumberText.appHandleColor().addTextChangedListener(this) binding.trackNumberText.appHandleColor().addTextChangedListener(this)
binding.discNumberText.appHandleColor().addTextChangedListener(this)
binding.lyricsText.appHandleColor().addTextChangedListener(this) binding.lyricsText.appHandleColor().addTextChangedListener(this)
binding.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() {
@ -85,16 +103,50 @@ class SongTagEditorActivity : AbsTagEditorActivity<ActivitySongTagEditorBinding>
binding.genreText.setText(genreName) binding.genreText.setText(genreName)
binding.yearText.setText(songYear) binding.yearText.setText(songYear)
binding.trackNumberText.setText(trackNumber) binding.trackNumberText.setText(trackNumber)
binding.discNumberText.setText(discNumber)
binding.lyricsText.setText(lyrics) binding.lyricsText.setText(lyrics)
binding.songComposerText.setText(composer) binding.songComposerText.setText(composer)
println(songTitle + songYear) println(songTitle + songYear)
} }
override fun loadCurrentImage() {} override fun loadCurrentImage() {
val bitmap = albumArt
setImageBitmap(
bitmap,
RetroColorUtil.getColor(
RetroColorUtil.generatePalette(bitmap),
defaultFooterColor()
)
)
deleteAlbumArt = false
}
override fun searchImageOnWeb() {} override fun searchImageOnWeb() {
searchWebFor(binding.songText.text.toString(), binding.artistText.text.toString())
}
override fun deleteImage() {} override fun deleteImage() {
setImageBitmap(
BitmapFactory.decodeResource(resources, R.drawable.default_audio_art),
defaultFooterColor()
)
deleteAlbumArt = true
dataChanged()
}
override fun setColors(color: Int) {
super.setColors(color)
saveFab.backgroundTintList = ColorStateList.valueOf(color)
ColorStateList.valueOf(
MaterialValueHelper.getPrimaryTextColor(
this,
color.isColorLight
)
).also {
saveFab.iconTint = it
saveFab.setTextColor(it)
}
}
override fun save() { override fun save() {
val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java) val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java)
@ -104,10 +156,17 @@ class SongTagEditorActivity : AbsTagEditorActivity<ActivitySongTagEditorBinding>
fieldKeyValueMap[FieldKey.GENRE] = binding.genreText.text.toString() fieldKeyValueMap[FieldKey.GENRE] = binding.genreText.text.toString()
fieldKeyValueMap[FieldKey.YEAR] = binding.yearText.text.toString() fieldKeyValueMap[FieldKey.YEAR] = binding.yearText.text.toString()
fieldKeyValueMap[FieldKey.TRACK] = binding.trackNumberText.text.toString() fieldKeyValueMap[FieldKey.TRACK] = binding.trackNumberText.text.toString()
fieldKeyValueMap[FieldKey.DISC_NO] = binding.discNumberText.text.toString()
fieldKeyValueMap[FieldKey.LYRICS] = binding.lyricsText.text.toString() fieldKeyValueMap[FieldKey.LYRICS] = binding.lyricsText.text.toString()
fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = binding.albumArtistText.text.toString() fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = binding.albumArtistText.text.toString()
fieldKeyValueMap[FieldKey.COMPOSER] = binding.songComposerText.text.toString() fieldKeyValueMap[FieldKey.COMPOSER] = binding.songComposerText.text.toString()
writeValuesToFiles(fieldKeyValueMap, null) writeValuesToFiles(
fieldKeyValueMap, when {
deleteAlbumArt -> ArtworkInfo(id, null)
albumArtBitmap == null -> null
else -> ArtworkInfo(id, albumArtBitmap!!)
}
)
} }
override fun getSongPaths(): List<String> = listOf(songRepository.song(id).data) override fun getSongPaths(): List<String> = listOf(songRepository.song(id).data)
@ -115,6 +174,35 @@ class SongTagEditorActivity : AbsTagEditorActivity<ActivitySongTagEditorBinding>
override fun getSongUris(): List<Uri> = listOf(MusicUtil.getSongFileUri(id)) override fun getSongUris(): List<Uri> = listOf(MusicUtil.getSongFileUri(id))
override fun loadImageFromFile(selectedFile: Uri?) { override fun loadImageFromFile(selectedFile: Uri?) {
GlideApp.with(this@SongTagEditorActivity).asBitmapPalette().load(selectedFile)
.diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true)
.into(object : ImageViewTarget<BitmapPaletteWrapper>(binding.editorImage) {
override fun onResourceReady(
resource: BitmapPaletteWrapper,
transition: Transition<in BitmapPaletteWrapper>?
) {
RetroColorUtil.getColor(resource.palette, Color.TRANSPARENT)
albumArtBitmap = resource.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) }
setImageBitmap(
albumArtBitmap,
RetroColorUtil.getColor(
resource.palette,
defaultFooterColor()
)
)
deleteAlbumArt = false
dataChanged()
setResult(Activity.RESULT_OK)
}
override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(errorDrawable)
Toast.makeText(this@SongTagEditorActivity, "Load Failed", Toast.LENGTH_LONG)
.show()
}
override fun setResource(resource: BitmapPaletteWrapper?) {}
})
} }
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
@ -131,6 +219,6 @@ class SongTagEditorActivity : AbsTagEditorActivity<ActivitySongTagEditorBinding>
val TAG: String = SongTagEditorActivity::class.java.simpleName val TAG: String = SongTagEditorActivity::class.java.simpleName
} }
override val editorImage: ImageView? override val editorImage: ImageView
get() = null get() = binding.editorImage
} }

View file

@ -21,8 +21,8 @@ import org.jaudiotagger.audio.exceptions.CannotWriteException
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 org.jaudiotagger.tag.images.AndroidArtwork
import org.jaudiotagger.tag.images.Artwork import org.jaudiotagger.tag.images.Artwork
import org.jaudiotagger.tag.images.ArtworkFactory
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.IOException import java.io.IOException
@ -58,11 +58,11 @@ class TagWriter {
try { try {
albumArtFile = createAlbumArtFile(context).canonicalFile albumArtFile = createAlbumArtFile(context).canonicalFile
info.artworkInfo.artwork.compress( info.artworkInfo.artwork.compress(
Bitmap.CompressFormat.PNG, Bitmap.CompressFormat.JPEG,
0, 100,
FileOutputStream(albumArtFile) FileOutputStream(albumArtFile)
) )
artwork = ArtworkFactory.createArtworkFromFile(albumArtFile) artwork = AndroidArtwork.createArtworkFromFile(albumArtFile)
} catch (e: IOException) { } catch (e: IOException) {
e.printStackTrace() e.printStackTrace()
} }
@ -131,11 +131,11 @@ class TagWriter {
try { try {
albumArtFile = createAlbumArtFile(context).canonicalFile albumArtFile = createAlbumArtFile(context).canonicalFile
info.artworkInfo.artwork.compress( info.artworkInfo.artwork.compress(
Bitmap.CompressFormat.PNG, Bitmap.CompressFormat.JPEG,
0, 100,
FileOutputStream(albumArtFile) FileOutputStream(albumArtFile)
) )
artwork = ArtworkFactory.createArtworkFromFile(albumArtFile) artwork = AndroidArtwork.createArtworkFromFile(albumArtFile)
} catch (e: IOException) { } catch (e: IOException) {
e.printStackTrace() e.printStackTrace()
} }

View file

@ -33,9 +33,10 @@ import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension 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.Album
import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.repository.PlaylistSongsLoader import code.name.monkey.retromusic.model.Genre
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import java.util.* import java.util.*

View file

@ -24,7 +24,6 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.fragments.AlbumCoverStyle import code.name.monkey.retromusic.fragments.AlbumCoverStyle
import code.name.monkey.retromusic.fragments.NowPlayingScreen.* import code.name.monkey.retromusic.fragments.NowPlayingScreen.*
import code.name.monkey.retromusic.fragments.base.goToLyrics import code.name.monkey.retromusic.fragments.base.goToLyrics
@ -36,7 +35,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.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
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
@ -91,7 +89,6 @@ class AlbumCoverPagerAdapter(
private lateinit var song: Song private lateinit var song: Song
private var colorReceiver: ColorReceiver? = null private var colorReceiver: ColorReceiver? = null
private var request: Int = 0 private var request: Int = 0
private val mainActivity get() = activity as MainActivity
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -108,11 +105,6 @@ class AlbumCoverPagerAdapter(
val view = inflater.inflate(getLayoutWithPlayerTheme(), container, false) val view = inflater.inflate(getLayoutWithPlayerTheme(), container, false)
ViewCompat.setTransitionName(view, "lyrics") ViewCompat.setTransitionName(view, "lyrics")
albumCover = view.findViewById(R.id.player_image) albumCover = view.findViewById(R.id.player_image)
view.setOnClickListener {
if (mainActivity.getBottomSheetBehavior().state == STATE_EXPANDED) {
showLyricsDialog()
}
}
return view return view
} }
@ -127,7 +119,6 @@ class AlbumCoverPagerAdapter(
setTitle(song.title) setTitle(song.title)
setMessage(if (data.isNullOrEmpty()) "No lyrics found" else data) setMessage(if (data.isNullOrEmpty()) "No lyrics found" else data)
setNegativeButton(R.string.synced_lyrics) { _, _ -> setNegativeButton(R.string.synced_lyrics) { _, _ ->
goToLyrics(requireActivity()) goToLyrics(requireActivity())
} }
show() show()
@ -139,6 +130,7 @@ class AlbumCoverPagerAdapter(
private fun getLayoutWithPlayerTheme(): Int { private fun getLayoutWithPlayerTheme(): Int {
return when (PreferenceUtil.nowPlayingScreen) { return when (PreferenceUtil.nowPlayingScreen) {
Card, Fit, Tiny, Classic, Gradient, Full -> R.layout.fragment_album_full_cover Card, Fit, Tiny, Classic, Gradient, Full -> R.layout.fragment_album_full_cover
Peak -> R.layout.fragment_peak_album_cover
else -> { else -> {
if (PreferenceUtil.isCarouselEffect) { if (PreferenceUtil.isCarouselEffect) {
R.layout.fragment_album_carousel_cover R.layout.fragment_album_carousel_cover

View file

@ -4,9 +4,9 @@ import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.PopupMenu
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.widget.AppCompatImageView import androidx.appcompat.widget.AppCompatImageView
import androidx.appcompat.widget.PopupMenu
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

View file

@ -7,8 +7,8 @@ import android.view.MenuItem
import androidx.annotation.MenuRes import androidx.annotation.MenuRes
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.interfaces.ICabCallback import code.name.monkey.retromusic.interfaces.ICabCallback
import code.name.monkey.retromusic.interfaces.ICabHolder import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.util.RetroColorUtil import code.name.monkey.retromusic.util.RetroColorUtil
@ -25,7 +25,7 @@ abstract class AbsMultiSelectAdapter<V : RecyclerView.ViewHolder?, I>(
private var menuRes: Int private var menuRes: Int
override fun onCabCreated(cab: AttachedCab, menu: Menu): Boolean { override fun onCabCreated(cab: AttachedCab, menu: Menu): Boolean {
activity.window.statusBarColor = activity.window.statusBarColor =
RetroColorUtil.shiftBackgroundColor(ATHUtil.resolveColor(activity, R.attr.colorSurface)) RetroColorUtil.shiftBackgroundColor(activity.surfaceColor())
return true return true
} }

View file

@ -35,12 +35,15 @@ 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.GlideApp
import code.name.monkey.retromusic.glide.playlistPreview.PlaylistPreview import code.name.monkey.retromusic.glide.playlistPreview.PlaylistPreview
import code.name.monkey.retromusic.helper.SortOrder.PlaylistSortOrder
import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper import code.name.monkey.retromusic.helper.menu.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.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import me.zhanghai.android.fastscroll.PopupTextProvider
class PlaylistAdapter( class PlaylistAdapter(
override val activity: FragmentActivity, override val activity: FragmentActivity,
@ -52,7 +55,7 @@ class PlaylistAdapter(
activity, activity,
ICabHolder, ICabHolder,
R.menu.menu_playlists_selection R.menu.menu_playlists_selection
) { ), PopupTextProvider {
init { init {
setHasStableIds(true) setHasStableIds(true)
@ -84,6 +87,17 @@ class PlaylistAdapter(
return MusicUtil.getPlaylistInfoString(activity, playlist.songs.toSongs()) return MusicUtil.getPlaylistInfoString(activity, playlist.songs.toSongs())
} }
override fun getPopupText(position: Int): String {
val sectionName: String = when (PreferenceUtil.playlistSortOrder) {
PlaylistSortOrder.PLAYLIST_A_Z, PlaylistSortOrder.PLAYLIST_Z_A -> dataSet[position].playlistEntity.playlistName
PlaylistSortOrder.PLAYLIST_SONG_COUNT, PlaylistSortOrder.PLAYLIST_SONG_COUNT_DESC -> dataSet[position].songs.size.toString()
else -> {
return ""
}
}
return MusicUtil.getSectionName(sectionName)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val playlist = dataSet[position] val playlist = dataSet[position]
holder.itemView.isActivated = isChecked(playlist) holder.itemView.isActivated = isChecked(playlist)

View file

@ -35,7 +35,7 @@ class CastOptionsProvider : OptionsProvider {
.build() .build()
} }
override fun getAdditionalSessionProviders(context: Context?): List<SessionProvider>? { override fun getAdditionalSessionProviders(context: Context): MutableList<SessionProvider>? {
return null return null
} }
} }

View file

@ -26,7 +26,7 @@ interface PlaylistDao {
suspend fun renamePlaylist(playlistId: Long, name: String) suspend fun renamePlaylist(playlistId: Long, name: String)
@Query("SELECT * FROM PlaylistEntity WHERE playlist_name = :name") @Query("SELECT * FROM PlaylistEntity WHERE playlist_name = :name")
fun isPlaylistExists(name: String): List<PlaylistEntity> fun playlist(name: String): List<PlaylistEntity>
@Query("SELECT * FROM PlaylistEntity") @Query("SELECT * FROM PlaylistEntity")
suspend fun playlists(): List<PlaylistEntity> suspend fun playlists(): List<PlaylistEntity>

View file

@ -65,9 +65,8 @@ class CreatePlaylistDialog : DialogFragment() {
val playlistName = playlistView.text.toString() val playlistName = playlistView.text.toString()
if (!TextUtils.isEmpty(playlistName)) { if (!TextUtils.isEmpty(playlistName)) {
libraryViewModel.addToPlaylist(playlistName, songs) libraryViewModel.addToPlaylist(playlistName, songs)
} else { } else {
playlistContainer.error = "Playlist is can't be empty" playlistContainer.error = "Playlist name can't be empty"
} }
} }
.create() .create()

View file

@ -30,7 +30,11 @@ class ImportPlaylistDialog : DialogFragment() {
return materialDialog(R.string.import_playlist) return materialDialog(R.string.import_playlist)
.setMessage(R.string.import_playlist_message) .setMessage(R.string.import_playlist_message)
.setPositiveButton(R.string.import_label) { _, _ -> .setPositiveButton(R.string.import_label) { _, _ ->
try {
libraryViewModel.importPlaylists() libraryViewModel.importPlaylists()
} catch (e: Exception) {
e.printStackTrace()
}
} }
.create() .create()
.colorButtons() .colorButtons()

View file

@ -14,14 +14,16 @@
*/ */
package code.name.monkey.retromusic.extensions package code.name.monkey.retromusic.extensions
import android.R
import android.app.Activity import android.app.Activity
import android.view.View
import android.view.ViewGroup
import androidx.annotation.DimenRes import androidx.annotation.DimenRes
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.appbar.MaterialToolbar
fun AppCompatActivity.applyToolbar(toolbar: MaterialToolbar) { fun AppCompatActivity.applyToolbar(toolbar: MaterialToolbar) {
//toolbar.setBackgroundColor(surfaceColor())
ToolbarContentTintHelper.colorBackButton(toolbar) ToolbarContentTintHelper.colorBackButton(toolbar)
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
} }
@ -39,3 +41,5 @@ inline fun <reified T : Any> Activity.extraNotNull(key: String, default: T? = nu
fun Activity.dip(@DimenRes id: Int): Int { fun Activity.dip(@DimenRes id: Int): Int {
return resources.getDimensionPixelSize(id) return resources.getDimensionPixelSize(id)
} }
inline val Activity.rootView: View get() = findViewById<ViewGroup>(R.id.content).getChildAt(0)

View file

@ -9,15 +9,13 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.isGone
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import code.name.monkey.appthemehelper.ATH import code.name.monkey.appthemehelper.ATH
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.ColorUtil
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.R
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil
fun AppCompatActivity.toggleScreenOn() { fun AppCompatActivity.toggleScreenOn() {
if (PreferenceUtil.isScreenOnEnabled) { if (PreferenceUtil.isScreenOnEnabled) {
@ -43,12 +41,10 @@ fun AppCompatActivity.setImmersiveFullscreen() {
fun AppCompatActivity.exitFullscreen() { fun AppCompatActivity.exitFullscreen() {
WindowInsetsControllerCompat(window, window.decorView).apply { WindowInsetsControllerCompat(window, window.decorView).apply {
systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
show(WindowInsetsCompat.Type.systemBars()) show(WindowInsetsCompat.Type.systemBars())
} }
} }
fun AppCompatActivity.hideStatusBar() { fun AppCompatActivity.hideStatusBar() {
hideStatusBar(PreferenceUtil.isFullScreenMode) hideStatusBar(PreferenceUtil.isFullScreenMode)
} }
@ -56,13 +52,26 @@ fun AppCompatActivity.hideStatusBar() {
private fun AppCompatActivity.hideStatusBar(fullscreen: Boolean) { private fun AppCompatActivity.hideStatusBar(fullscreen: Boolean) {
val statusBar = window.decorView.rootView.findViewById<View>(R.id.status_bar) val statusBar = window.decorView.rootView.findViewById<View>(R.id.status_bar)
if (statusBar != null) { if (statusBar != null) {
statusBar.visibility = if (fullscreen) View.GONE else View.VISIBLE statusBar.isGone = fullscreen
} }
} }
fun AppCompatActivity.setDrawUnderStatusBar() { fun AppCompatActivity.setDrawBehindSystemBars() {
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)
if (VersionUtils.hasOreo()) {
if (VersionUtils.hasQ()) {
window.isNavigationBarContrastEnforced = false
}
window.navigationBarColor = Color.TRANSPARENT
window.statusBarColor = Color.TRANSPARENT window.statusBarColor = Color.TRANSPARENT
} else {
setNavigationBarColorPreOreo(surfaceColor())
if (VersionUtils.hasMarshmallow()) {
setStatusBarColor(Color.TRANSPARENT)
} else {
setStatusBarColor(surfaceColor())
}
}
} }
fun FragmentActivity.setTaskDescriptionColor(color: Int) { fun FragmentActivity.setTaskDescriptionColor(color: Int) {
@ -100,9 +109,11 @@ fun AppCompatActivity.setLightStatusBarAuto(bgColor: Int) {
} }
fun AppCompatActivity.setLightNavigationBar(enabled: Boolean) { fun AppCompatActivity.setLightNavigationBar(enabled: Boolean) {
if (!ATHUtil.isWindowBackgroundDark(this) and ThemeStore.coloredNavigationBar(this)) { ATH.setLightNavigationBar(this, enabled)
ATH.setLightNavigationbar(this, enabled) }
}
fun AppCompatActivity.setLightNavigationBarAuto(bgColor: Int) {
setLightNavigationBar(ColorUtil.isColorLight(bgColor))
} }
@ -129,11 +140,39 @@ fun AppCompatActivity.setStatusBarColor(color: Int) {
else -> window.statusBarColor = ColorUtil.darkenColor(color) else -> window.statusBarColor = ColorUtil.darkenColor(color)
} }
} }
setLightStatusBarAuto(ATHUtil.resolveColor(this, R.attr.colorSurface)) setLightStatusBarAuto(surfaceColor())
} }
fun AppCompatActivity.setStatusBarColorAuto() { fun AppCompatActivity.setStatusBarColorAuto() {
// we don't want to use statusbar color because we are doing the color darkening on our own to support KitKat // we don't want to use statusbar color because we are doing the color darkening on our own to support KitKat
setStatusBarColor(ATHUtil.resolveColor(this, R.attr.colorSurface)) setStatusBarColor(surfaceColor())
setLightStatusBarAuto(ATHUtil.resolveColor(this, R.attr.colorSurface)) setLightStatusBarAuto(surfaceColor())
}
fun AppCompatActivity.setNavigationBarColor(color: Int) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
window.navigationBarColor = color
} else {
window.navigationBarColor = ColorUtil.darkenColor(color)
}
setLightNavigationBarAuto(color)
}
fun AppCompatActivity.setNavigationBarColorPreOreo(color: Int) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
window.navigationBarColor = ColorUtil.darkenColor(color)
}
}
fun AppCompatActivity.setStatusBarColorPreMarshmallow(color: Int) {
val statusBar = window.decorView.rootView.findViewById<View>(R.id.status_bar)
if (statusBar != null) {
statusBar.setBackgroundColor(
ColorUtil.darkenColor(
color
)
)
} else {
window.statusBarColor = ColorUtil.darkenColor(color)
}
} }

View file

@ -65,6 +65,10 @@ fun Context.surfaceColor() = resolveColor(R.attr.colorSurface, Color.WHITE)
fun Fragment.surfaceColor() = resolveColor(R.attr.colorSurface, Color.WHITE) fun Fragment.surfaceColor() = resolveColor(R.attr.colorSurface, Color.WHITE)
fun Context.surfaceColor(fallBackColor: Int) = resolveColor(R.attr.colorSurface, fallBackColor)
fun Fragment.surfaceColor(fallBackColor: Int) = resolveColor(R.attr.colorSurface, fallBackColor)
fun Context.textColorSecondary() = resolveColor(android.R.attr.textColorSecondary) fun Context.textColorSecondary() = resolveColor(android.R.attr.textColorSecondary)
fun Fragment.textColorSecondary() = resolveColor(android.R.attr.textColorSecondary) fun Fragment.textColorSecondary() = resolveColor(android.R.attr.textColorSecondary)
@ -77,6 +81,10 @@ fun Context.textColorPrimary() = resolveColor(android.R.attr.textColorPrimary)
fun Fragment.textColorPrimary() = resolveColor(android.R.attr.textColorPrimary) fun Fragment.textColorPrimary() = resolveColor(android.R.attr.textColorPrimary)
fun Context.defaultFooterColor() = resolveColor(R.attr.defaultFooterColor)
fun Fragment.defaultFooterColor() = resolveColor(R.attr.defaultFooterColor)
fun Context.resolveColor(@AttrRes attr: Int, fallBackColor: Int = 0) = fun Context.resolveColor(@AttrRes attr: Int, fallBackColor: Int = 0) =
ATHUtil.resolveColor(this, attr, fallBackColor) ATHUtil.resolveColor(this, attr, fallBackColor)
@ -120,6 +128,15 @@ fun MaterialButton.accentOutlineColor() {
rippleColor = colorStateList rippleColor = colorStateList
} }
fun MaterialButton.elevatedAccentColor() {
if (materialYou) return
val color = context.darkAccentColorVariant()
rippleColor = ColorStateList.valueOf(color)
setBackgroundColor(color)
setTextColor(MaterialValueHelper.getPrimaryTextColor(context, color.isColorLight))
iconTint = ColorStateList.valueOf(context.accentColor())
}
fun SeekBar.applyColor(@ColorInt color: Int) { fun SeekBar.applyColor(@ColorInt color: Int) {
thumbTintList = ColorStateList.valueOf(color) thumbTintList = ColorStateList.valueOf(color)
progressTintList = ColorStateList.valueOf(color) progressTintList = ColorStateList.valueOf(color)
@ -234,5 +251,14 @@ fun Context.darkAccentColor(): Int {
) )
} }
@ColorInt
fun Context.darkAccentColorVariant(): Int {
return ColorUtils.blendARGB(
accentColor(),
surfaceColor(),
if (surfaceColor().isColorLight) 0.9f else 0.95f
)
}
inline val @receiver:ColorInt Int.isColorLight inline val @receiver:ColorInt Int.isColorLight
get() = ColorUtil.isColorLight(this) get() = ColorUtil.isColorLight(this)

View file

@ -20,7 +20,7 @@ import android.database.Cursor
internal fun Cursor.getInt(columnName: String): Int { internal fun Cursor.getInt(columnName: String): Int {
try { try {
return this.getInt(this.getColumnIndex(columnName)) return getInt(getColumnIndexOrThrow(columnName))
} catch (ex: Throwable) { } catch (ex: Throwable) {
throw IllegalStateException("invalid column $columnName", ex) throw IllegalStateException("invalid column $columnName", ex)
} }
@ -28,7 +28,7 @@ internal fun Cursor.getInt(columnName: String): Int {
internal fun Cursor.getLong(columnName: String): Long { internal fun Cursor.getLong(columnName: String): Long {
try { try {
return this.getLong(this.getColumnIndex(columnName)) return getLong(getColumnIndexOrThrow(columnName))
} catch (ex: Throwable) { } catch (ex: Throwable) {
throw IllegalStateException("invalid column $columnName", ex) throw IllegalStateException("invalid column $columnName", ex)
} }
@ -36,7 +36,7 @@ internal fun Cursor.getLong(columnName: String): Long {
internal fun Cursor.getString(columnName: String): String { internal fun Cursor.getString(columnName: String): String {
try { try {
return this.getString(this.getColumnIndex(columnName)) return getString(getColumnIndexOrThrow(columnName))
} catch (ex: Throwable) { } catch (ex: Throwable) {
throw IllegalStateException("invalid column $columnName", ex) throw IllegalStateException("invalid column $columnName", ex)
} }
@ -44,7 +44,7 @@ internal fun Cursor.getString(columnName: String): String {
internal fun Cursor.getStringOrNull(columnName: String): String? { internal fun Cursor.getStringOrNull(columnName: String): String? {
try { try {
return this.getString(this.getColumnIndex(columnName)) return getString(getColumnIndexOrThrow(columnName))
} catch (ex: Throwable) { } catch (ex: Throwable) {
throw IllegalStateException("invalid column $columnName", ex) throw IllegalStateException("invalid column $columnName", ex)
} }

View file

@ -66,8 +66,7 @@ val FragmentManager.currentNavigationFragment: Fragment?
fun AppCompatActivity.currentFragment(navHostId: Int): Fragment? { fun AppCompatActivity.currentFragment(navHostId: Int): Fragment? {
val navHostFragment: NavHostFragment = val navHostFragment: NavHostFragment =
supportFragmentManager.findFragmentById(navHostId) as NavHostFragment supportFragmentManager.findFragmentById(navHostId) as NavHostFragment
navHostFragment.targetFragment return navHostFragment.childFragmentManager.fragments.firstOrNull()
return navHostFragment.childFragmentManager.fragments.first()
} }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")

View file

@ -2,7 +2,6 @@ package code.name.monkey.retromusic.extensions
import android.net.Uri import android.net.Uri
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import androidx.fragment.app.Fragment
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 org.jaudiotagger.audio.AudioFileIO

View file

@ -1,9 +1,12 @@
package code.name.monkey.retromusic.extensions package code.name.monkey.retromusic.extensions
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.PreferenceUtil
fun WindowInsetsCompat?.safeGetBottomInsets(): Int { fun WindowInsetsCompat?.safeGetBottomInsets(): Int {
// Get Navbar heights if insets are null return if (PreferenceUtil.isFullScreenMode) {
return (this?.getInsets(WindowInsetsCompat.Type.systemBars())?.bottom ?: RetroUtil.getNavigationBarHeight()) return 0
} else {
this?.getInsets(WindowInsetsCompat.Type.systemBars())?.bottom ?: 0
}
} }

View file

@ -28,6 +28,7 @@ import code.name.monkey.retromusic.repository.RealRepository
import code.name.monkey.retromusic.util.DensityUtil import code.name.monkey.retromusic.util.DensityUtil
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 import kotlinx.coroutines.withContext
import java.io.File import java.io.File
@ -233,6 +234,7 @@ class LibraryViewModel(
suspend fun artistById(id: Long) = repository.artistById(id) suspend fun artistById(id: Long) = repository.artistById(id)
suspend fun favoritePlaylist() = repository.favoritePlaylist() suspend fun favoritePlaylist() = repository.favoritePlaylist()
suspend fun isFavoriteSong(song: SongEntity) = repository.isFavoriteSong(song) suspend fun isFavoriteSong(song: SongEntity) = repository.isFavoriteSong(song)
suspend fun isSongFavorite(songId: Long) = repository.isSongFavorite(songId)
suspend fun insertSongs(songs: List<SongEntity>) = repository.insertSongs(songs) suspend fun insertSongs(songs: List<SongEntity>) = repository.insertSongs(songs)
suspend fun removeSongFromPlaylist(songEntity: SongEntity) = suspend fun removeSongFromPlaylist(songEntity: SongEntity) =
repository.removeSongFromPlaylist(songEntity) repository.removeSongFromPlaylist(songEntity)
@ -356,6 +358,13 @@ class LibraryViewModel(
it.toSongEntity(playListId = playlist.playListId) it.toSongEntity(playListId = playlist.playListId)
}) })
} }
withContext(Main) {
Toast.makeText(
App.getContext(),
"Playlist already exists",
Toast.LENGTH_SHORT
).show()
if (songs.isNotEmpty()) {
Toast.makeText( Toast.makeText(
App.getContext(), App.getContext(),
"Adding songs to $playlistName", "Adding songs to $playlistName",
@ -363,6 +372,9 @@ class LibraryViewModel(
).show() ).show()
} }
} }
}
}
} }
fun setFabMargin(bottomMargin: Int) { fun setFabMargin(bottomMargin: Int) {

View file

@ -22,24 +22,26 @@ enum class NowPlayingScreen constructor(
@param:StringRes @field:StringRes @param:StringRes @field:StringRes
val titleRes: Int, val titleRes: Int,
@param:DrawableRes @field:DrawableRes val drawableResId: Int, @param:DrawableRes @field:DrawableRes val drawableResId: Int,
val id: Int val id: Int,
val defaultCoverTheme: AlbumCoverStyle?
) { ) {
// Some Now playing themes look better with particular Album cover theme
Adaptive(R.string.adaptive, R.drawable.np_adaptive, 10), Adaptive(R.string.adaptive, R.drawable.np_adaptive, 10, AlbumCoverStyle.FullCard),
Blur(R.string.blur, R.drawable.np_blur, 4), Blur(R.string.blur, R.drawable.np_blur, 4, AlbumCoverStyle.Normal),
BlurCard(R.string.blur_card, R.drawable.np_blur_card, 9), BlurCard(R.string.blur_card, R.drawable.np_blur_card, 9, AlbumCoverStyle.Card),
Card(R.string.card, R.drawable.np_card, 6), Card(R.string.card, R.drawable.np_card, 6, AlbumCoverStyle.Full),
Circle(R.string.circle, R.drawable.np_minimalistic_circle, 15), Circle(R.string.circle, R.drawable.np_minimalistic_circle, 15, null),
Classic(R.string.classic, R.drawable.np_classic, 16), Classic(R.string.classic, R.drawable.np_classic, 16, AlbumCoverStyle.Full),
Color(R.string.color, R.drawable.np_color, 5), Color(R.string.color, R.drawable.np_color, 5, AlbumCoverStyle.Normal),
Fit(R.string.fit, R.drawable.np_fit, 12), Fit(R.string.fit, R.drawable.np_fit, 12, AlbumCoverStyle.Full),
Flat(R.string.flat, R.drawable.np_flat, 1), Flat(R.string.flat, R.drawable.np_flat, 1, AlbumCoverStyle.Flat),
Full(R.string.full, R.drawable.np_full, 2), Full(R.string.full, R.drawable.np_full, 2, AlbumCoverStyle.Full),
Gradient(R.string.gradient, R.drawable.np_gradient, 17), Gradient(R.string.gradient, R.drawable.np_gradient, 17, AlbumCoverStyle.Full),
Material(R.string.material, R.drawable.np_material, 11), Material(R.string.material, R.drawable.np_material, 11, AlbumCoverStyle.Normal),
Normal(R.string.normal, R.drawable.np_normal, 0), Normal(R.string.normal, R.drawable.np_normal, 0, AlbumCoverStyle.Normal),
Peak(R.string.peak, R.drawable.np_peak, 14), Peak(R.string.peak, R.drawable.np_peak, 14, AlbumCoverStyle.Normal),
Plain(R.string.plain, R.drawable.np_plain, 3), Plain(R.string.plain, R.drawable.np_plain, 3, AlbumCoverStyle.Normal),
Simple(R.string.simple, R.drawable.np_simple, 8), Simple(R.string.simple, R.drawable.np_simple, 8, AlbumCoverStyle.Normal),
Tiny(R.string.tiny, R.drawable.np_tiny, 7), Tiny(R.string.tiny, R.drawable.np_tiny, 7, null),
} }

View file

@ -24,7 +24,6 @@ import androidx.recyclerview.widget.GridLayoutManager
import code.name.monkey.retromusic.EXTRA_ALBUM_ID import code.name.monkey.retromusic.EXTRA_ALBUM_ID
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.album.AlbumAdapter import code.name.monkey.retromusic.adapter.album.AlbumAdapter
import code.name.monkey.retromusic.extensions.navigate
import code.name.monkey.retromusic.extensions.surfaceColor import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.fragments.GridStyle import code.name.monkey.retromusic.fragments.GridStyle
import code.name.monkey.retromusic.fragments.ReloadType import code.name.monkey.retromusic.fragments.ReloadType

View file

@ -83,10 +83,6 @@ abstract class AbsArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragm
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
_binding = FragmentArtistDetailsBinding.bind(view) _binding = FragmentArtistDetailsBinding.bind(view)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setHasOptionsMenu(true) setHasOptionsMenu(true)
mainActivity.addMusicServiceEventListener(detailsViewModel) mainActivity.addMusicServiceEventListener(detailsViewModel)
mainActivity.setSupportActionBar(binding.toolbar) mainActivity.setSupportActionBar(binding.toolbar)

View file

@ -7,7 +7,7 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog import androidx.core.net.toUri
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
@ -49,7 +49,9 @@ class BackupFragment : Fragment(R.layout.fragment_backup), BackupAdapter.BackupC
val openFilePicker = registerForActivityResult(ActivityResultContracts.OpenDocument()) { val openFilePicker = registerForActivityResult(ActivityResultContracts.OpenDocument()) {
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
it?.let { it?.let {
backupViewModel.restoreBackup(requireActivity(), requireContext().contentResolver.openInputStream(it)) startActivity(Intent(context, RestoreActivity::class.java).apply {
data = it
})
} }
} }
} }
@ -103,18 +105,12 @@ class BackupFragment : Fragment(R.layout.fragment_backup), BackupAdapter.BackupC
} }
override fun onBackupClicked(file: File) { override fun onBackupClicked(file: File) {
AlertDialog.Builder(requireContext())
.setTitle(R.string.restore)
.setMessage(R.string.restore_message)
.setPositiveButton(R.string.restore) { _, _ ->
lifecycleScope.launch { lifecycleScope.launch {
backupViewModel.restoreBackup(requireActivity(), file.inputStream()) startActivity(Intent(context, RestoreActivity::class.java).apply {
data = file.toUri()
})
} }
} }
.setNegativeButton(android.R.string.cancel, null)
.create()
.show()
}
@SuppressLint("CheckResult") @SuppressLint("CheckResult")
override fun onBackupMenuClicked(file: File, menuItem: MenuItem): Boolean { override fun onBackupMenuClicked(file: File, menuItem: MenuItem): Boolean {

View file

@ -5,6 +5,8 @@ import android.content.Intent
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.helper.BackupContent
import code.name.monkey.retromusic.helper.BackupHelper import code.name.monkey.retromusic.helper.BackupHelper
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -25,12 +27,12 @@ class BackupViewModel : ViewModel() {
} }
} }
suspend fun restoreBackup(activity: Activity, inputStream: InputStream?) { suspend fun restoreBackup(activity: Activity, inputStream: InputStream?, contents: List<BackupContent>) {
BackupHelper.restoreBackup(activity, inputStream) BackupHelper.restoreBackup(activity, inputStream, contents)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
val intent = Intent( val intent = Intent(
activity, activity,
activity::class.java MainActivity::class.java
) )
activity.startActivity(intent) activity.startActivity(intent)
exitProcess(0) exitProcess(0)

View file

@ -1,12 +1,89 @@
package code.name.monkey.retromusic.fragments.backup package code.name.monkey.retromusic.fragments.backup
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.provider.MediaStore
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import code.name.monkey.retromusic.R import androidx.appcompat.app.AppCompatDelegate
import androidx.lifecycle.lifecycleScope
import code.name.monkey.retromusic.databinding.ActivityRestoreBinding
import code.name.monkey.retromusic.helper.BackupContent
import code.name.monkey.retromusic.helper.BackupContent.*
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.theme.ThemeManager
import com.google.android.material.color.DynamicColors
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class RestoreActivity : AppCompatActivity() { class RestoreActivity : AppCompatActivity() {
lateinit var binding: ActivityRestoreBinding
private val backupViewModel: BackupViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
updateTheme()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_restore) binding = ActivityRestoreBinding.inflate(layoutInflater)
setContentView(binding.root)
val backupUri = intent?.data
binding.backupName.setText(getFileName(backupUri))
binding.cancelButton.setOnClickListener {
finish()
}
binding.restoreButton.setOnClickListener {
val backupContents = mutableListOf<BackupContent>()
if (binding.checkSettings.isChecked) backupContents.add(SETTINGS)
if (binding.checkQueue.isChecked) backupContents.add(QUEUE)
if (binding.checkDatabases.isChecked) backupContents.add(PLAYLISTS)
if (binding.checkArtistImages.isChecked) backupContents.add(CUSTOM_ARTIST_IMAGES)
if (binding.checkUserImages.isChecked) backupContents.add(USER_IMAGES)
lifecycleScope.launch(Dispatchers.IO) {
if (backupUri != null) {
contentResolver.openInputStream(backupUri)?.use {
backupViewModel.restoreBackup(this@RestoreActivity, it, backupContents)
}
}
}
}
}
private fun updateTheme() {
AppCompatDelegate.setDefaultNightMode(ThemeManager.getNightMode(this))
// Apply dynamic colors to activity if enabled
if (PreferenceUtil.materialYou) {
DynamicColors.applyIfAvailable(
this,
com.google.android.material.R.style.ThemeOverlay_Material3_DynamicColors_DayNight
)
}
}
private fun getFileName(uri: Uri?): String? {
when (uri?.scheme) {
"file" -> {
return uri.lastPathSegment
}
"content" -> {
val proj = arrayOf(MediaStore.Images.Media.TITLE)
contentResolver.query(
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
} else {
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
}, proj, null, null, null
)?.use { cursor ->
if (cursor.count != 0) {
val columnIndex: Int =
cursor.getColumnIndexOrThrow(MediaStore.Images.Media.TITLE)
cursor.moveToFirst()
return cursor.getString(columnIndex)
}
}
}
}
return "Backup"
} }
} }

View file

@ -17,13 +17,13 @@ package code.name.monkey.retromusic.fragments.base
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.annotation.LayoutRes import androidx.annotation.LayoutRes
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.VersionUtils 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.MainActivity import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.extensions.setLightStatusBarAuto import code.name.monkey.retromusic.extensions.setLightStatusBarAuto
import code.name.monkey.retromusic.extensions.setTaskDescriptionColorAuto import code.name.monkey.retromusic.extensions.setTaskDescriptionColorAuto
import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.fragments.LibraryViewModel import code.name.monkey.retromusic.fragments.LibraryViewModel
import org.koin.androidx.viewmodel.ext.android.sharedViewModel import org.koin.androidx.viewmodel.ext.android.sharedViewModel
@ -52,7 +52,7 @@ abstract class AbsMainActivityFragment(@LayoutRes layout: Int) : AbsMusicService
} }
fun setStatusBarColorAuto(view: View) { fun setStatusBarColorAuto(view: View) {
val colorPrimary = ATHUtil.resolveColor(requireContext(), R.attr.colorSurface) val colorPrimary = surfaceColor()
// we don't want to use statusbar color because we are doing the color darkening on our own to support KitKat // we don't want to use statusbar color because we are doing the color darkening on our own to support KitKat
if (VersionUtils.hasMarshmallow()) { if (VersionUtils.hasMarshmallow()) {
setStatusBarColor(view, colorPrimary) setStatusBarColor(view, colorPrimary)

View file

@ -25,7 +25,6 @@ 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.view.GestureDetector import android.view.GestureDetector
import android.view.MenuItem import android.view.MenuItem
import android.view.MotionEvent import android.view.MotionEvent
@ -33,6 +32,7 @@ import android.view.View
import android.widget.RelativeLayout import android.widget.RelativeLayout
import android.widget.Toast import android.widget.Toast
import androidx.annotation.LayoutRes import androidx.annotation.LayoutRes
import androidx.appcompat.graphics.drawable.DrawableWrapper
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
@ -46,18 +46,17 @@ 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.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.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.NowPlayingScreen
import code.name.monkey.retromusic.fragments.ReloadType import code.name.monkey.retromusic.fragments.ReloadType
import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.IPaletteColorHolder import code.name.monkey.retromusic.interfaces.IPaletteColorHolder
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
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.*
@ -67,7 +66,6 @@ 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 kotlin.math.abs import kotlin.math.abs
abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragment(layout), abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragment(layout),
@ -81,8 +79,12 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
val song = MusicPlayerRemote.currentSong val song = MusicPlayerRemote.currentSong
when (item.itemId) { when (item.itemId) {
R.id.action_toggle_lyrics -> { R.id.action_toggle_lyrics -> {
PreferenceUtil.showLyrics = !PreferenceUtil.showLyrics PreferenceUtil.showLyrics = !item.isChecked
showLyricsIcon(item) item.isChecked = !item.isChecked
return true
}
R.id.action_go_to_lyrics -> {
goToLyrics(requireActivity())
return true return true
} }
R.id.action_toggle_favorite -> { R.id.action_toggle_favorite -> {
@ -191,18 +193,6 @@ 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()
@ -219,7 +209,6 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
override fun onPlayingMetaChanged() { override fun onPlayingMetaChanged() {
updateIsFavorite() updateIsFavorite()
updateLyrics()
} }
override fun onFavoriteStateChanged() { override fun onFavoriteStateChanged() {
@ -231,7 +220,7 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
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.isSongFavorite(song.id)
if (isFavorite) { if (isFavorite) {
libraryViewModel.removeSongFromPlaylist(songEntity) libraryViewModel.removeSongFromPlaylist(songEntity)
} else { } else {
@ -245,11 +234,8 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
fun updateIsFavorite(animate: Boolean = false) { fun updateIsFavorite(animate: Boolean = false) {
lifecycleScope.launch(IO) { lifecycleScope.launch(IO) {
val playlist: PlaylistEntity = libraryViewModel.favoritePlaylist() val isFavorite: Boolean =
if (playlist != null) { libraryViewModel.isSongFavorite(MusicPlayerRemote.currentSong.id)
val song: SongEntity =
MusicPlayerRemote.currentSong.toSongEntity(playlist.playListId)
val isFavorite: Boolean = libraryViewModel.isFavoriteSong(song).isNotEmpty()
withContext(Main) { withContext(Main) {
val icon = if (animate) { val icon = if (animate) {
if (isFavorite) R.drawable.avd_favorite else R.drawable.avd_unfavorite if (isFavorite) R.drawable.avd_favorite else R.drawable.avd_unfavorite
@ -277,33 +263,6 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
} }
} }
} }
}
private fun updateLyrics() {
setLyrics(null)
lifecycleScope.launch(IO) {
val song = MusicPlayerRemote.currentSong
val lyrics = try {
var data: String? = LyricUtil.getStringFromFile(song.title, song.artistName)
if (TextUtils.isEmpty(data)) {
data = MusicUtil.getLyrics(song)
if (TextUtils.isEmpty(data)) {
null
} else {
Lyrics.parse(song, data)
}
} else Lyrics.parse(song, data!!)
} catch (err: FileNotFoundException) {
null
}
withContext(Main) {
setLyrics(lyrics)
}
}
}
open fun setLyrics(l: Lyrics?) {
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@ -320,8 +279,19 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
} }
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
override fun onStart() { override fun onResume() {
super.onStart() super.onResume()
val nps = PreferenceUtil.nowPlayingScreen
if (nps == NowPlayingScreen.Circle || nps == NowPlayingScreen.Peak || nps == NowPlayingScreen.Tiny) {
playerToolbar()?.menu?.removeItem(R.id.action_toggle_lyrics)
} else {
playerToolbar()?.menu?.findItem(R.id.action_toggle_lyrics)?.apply {
fixCheckStateOnIcon()
isCheckable = true
isChecked = PreferenceUtil.showLyrics
}
}
requireView().setOnTouchListener( requireView().setOnTouchListener(
SwipeDetector( SwipeDetector(
requireContext(), requireContext(),
@ -329,7 +299,6 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
requireView() requireView()
) )
) )
playerToolbar()?.menu?.findItem(R.id.action_toggle_lyrics)?.let { showLyricsIcon(it) }
} }
class SwipeDetector(val context: Context, val viewPager: ViewPager?, val view: View) : class SwipeDetector(val context: Context, val viewPager: ViewPager?, val view: View) :
@ -444,3 +413,14 @@ fun goToLyrics(activity: Activity) {
) )
} }
} }
/** Fixes checked state being ignored by injecting checked state directly into drawable */
@SuppressLint("RestrictedApi")
class CheckDrawableWrapper(val menuItem: MenuItem) : DrawableWrapper(menuItem.icon) {
// inject checked state into drawable state set
override fun setState(stateSet: IntArray) = super.setState(
if (menuItem.isChecked) stateSet + android.R.attr.state_checked else stateSet
)
}
/** Wrap icon drawable with [CheckDrawableWrapper]. */
fun MenuItem.fixCheckStateOnIcon() = apply { icon = CheckDrawableWrapper(this) }

View file

@ -34,6 +34,8 @@ import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.dip import code.name.monkey.retromusic.extensions.dip
import code.name.monkey.retromusic.extensions.drawNextToNavbar import code.name.monkey.retromusic.extensions.drawNextToNavbar
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.IScrollHelper
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.ThemedFastScroller.create import code.name.monkey.retromusic.util.ThemedFastScroller.create
import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.transition.MaterialFadeThrough import com.google.android.material.transition.MaterialFadeThrough
@ -42,7 +44,7 @@ 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), IScrollHelper {
private var _binding: FragmentMainRecyclerBinding? = null private var _binding: FragmentMainRecyclerBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
@ -56,15 +58,15 @@ abstract class AbsRecyclerViewFragment<A : RecyclerView.Adapter<*>, LM : Recycle
_binding = FragmentMainRecyclerBinding.bind(view) _binding = FragmentMainRecyclerBinding.bind(view)
postponeEnterTransition() postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() } view.doOnPreDraw { startPostponedEnterTransition() }
enterTransition = MaterialFadeThrough().apply { enterTransition = MaterialFadeThrough().addTarget(binding.recyclerView)
addTarget(binding.recyclerView) reenterTransition = MaterialFadeThrough().addTarget(binding.recyclerView)
}
mainActivity.setSupportActionBar(binding.toolbar) mainActivity.setSupportActionBar(binding.toolbar)
mainActivity.supportActionBar?.title = null mainActivity.supportActionBar?.title = null
initLayoutManager() initLayoutManager()
initAdapter() initAdapter()
setUpRecyclerView() setUpRecyclerView()
setupToolbar() setupToolbar()
binding.shuffleButton.fitsSystemWindows = PreferenceUtil.isFullScreenMode
// Add listeners when shuffle is visible // Add listeners when shuffle is visible
if (isShuffleVisible) { if (isShuffleVisible) {
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
@ -200,7 +202,7 @@ abstract class AbsRecyclerViewFragment<A : RecyclerView.Adapter<*>, LM : Recycle
val container get() = binding.root val container get() = binding.root
fun scrollToTop() { override fun scrollToTop() {
recyclerView.scrollToPosition(0) recyclerView.scrollToPosition(0)
binding.appBarLayout.setExpanded(true, true) binding.appBarLayout.setExpanded(true, true)
} }

View file

@ -21,9 +21,9 @@ import android.os.Environment
import android.text.Html import android.text.Html
import android.view.* import android.view.*
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import android.widget.PopupMenu
import android.widget.Toast import android.widget.Toast
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
import androidx.appcompat.widget.PopupMenu
import androidx.loader.app.LoaderManager import androidx.loader.app.LoaderManager
import androidx.loader.content.Loader import androidx.loader.content.Loader
import androidx.navigation.Navigation.findNavController import androidx.navigation.Navigation.findNavController
@ -31,7 +31,6 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.ThemeStore.Companion.accentColor import code.name.monkey.appthemehelper.ThemeStore.Companion.accentColor
import code.name.monkey.appthemehelper.common.ATHToolbarActivity import code.name.monkey.appthemehelper.common.ATHToolbarActivity
import code.name.monkey.appthemehelper.util.ATHUtil.resolveColor
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.App
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
@ -40,8 +39,7 @@ import code.name.monkey.retromusic.adapter.Storage
import code.name.monkey.retromusic.adapter.StorageAdapter import code.name.monkey.retromusic.adapter.StorageAdapter
import code.name.monkey.retromusic.adapter.StorageClickListener import code.name.monkey.retromusic.adapter.StorageClickListener
import code.name.monkey.retromusic.databinding.FragmentFolderBinding import code.name.monkey.retromusic.databinding.FragmentFolderBinding
import code.name.monkey.retromusic.extensions.drawNextToNavbar import code.name.monkey.retromusic.extensions.*
import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import code.name.monkey.retromusic.fragments.folder.FoldersFragment.ListPathsAsyncTask.OnPathsListedCallback import code.name.monkey.retromusic.fragments.folder.FoldersFragment.ListPathsAsyncTask.OnPathsListedCallback
import code.name.monkey.retromusic.fragments.folder.FoldersFragment.ListSongsAsyncTask.OnSongsListedCallback import code.name.monkey.retromusic.fragments.folder.FoldersFragment.ListSongsAsyncTask.OnSongsListedCallback
@ -107,9 +105,9 @@ class FoldersFragment : AbsMainActivityFragment(R.layout.fragment_folder),
mainActivity.addMusicServiceEventListener(libraryViewModel) mainActivity.addMusicServiceEventListener(libraryViewModel)
mainActivity.setSupportActionBar(binding.toolbar) mainActivity.setSupportActionBar(binding.toolbar)
mainActivity.supportActionBar?.title = null mainActivity.supportActionBar?.title = null
enterTransition = MaterialFadeThrough().apply { enterTransition = MaterialFadeThrough()
addTarget(binding.recyclerView) reenterTransition = MaterialFadeThrough()
}
setUpBreadCrumbs() setUpBreadCrumbs()
setUpRecyclerView() setUpRecyclerView()
setUpAdapter() setUpAdapter()
@ -188,7 +186,7 @@ class FoldersFragment : AbsMainActivityFragment(R.layout.fragment_folder),
} }
override fun onFileMenuClicked(file: File, view: View) { override fun onFileMenuClicked(file: File, view: View) {
val popupMenu = PopupMenu(activity, view) val popupMenu = PopupMenu(requireActivity(), view)
if (file.isDirectory) { if (file.isDirectory) {
popupMenu.inflate(R.menu.menu_item_directory) popupMenu.inflate(R.menu.menu_item_directory)
popupMenu.setOnMenuItemClickListener { item: MenuItem -> popupMenu.setOnMenuItemClickListener { item: MenuItem ->
@ -453,12 +451,10 @@ class FoldersFragment : AbsMainActivityFragment(R.layout.fragment_folder),
private fun checkForPadding() { private fun checkForPadding() {
val count = adapter?.itemCount ?: 0 val count = adapter?.itemCount ?: 0
if (_binding != null) { if (_binding != null) {
val params = binding.root.layoutParams as ViewGroup.MarginLayoutParams binding.recyclerView.updatePadding(
params.bottomMargin = if (count > 0 && playingQueue.isNotEmpty()) dip2px( bottom = if (count > 0 && playingQueue.isNotEmpty()) dip(R.dimen.mini_player_height_expanded)
requireContext(), else dip(R.dimen.mini_player_height_expanded)
104f )
) else dip2px(requireContext(), 54f)
binding.root.layoutParams = params
} }
} }
@ -526,24 +522,20 @@ class FoldersFragment : AbsMainActivityFragment(R.layout.fragment_folder),
private fun setUpBreadCrumbs() { private fun setUpBreadCrumbs() {
binding.breadCrumbs.setActivatedContentColor( binding.breadCrumbs.setActivatedContentColor(
resolveColor(requireContext(), android.R.attr.textColorPrimary) textColorPrimary()
) )
binding.breadCrumbs.setDeactivatedContentColor( binding.breadCrumbs.setDeactivatedContentColor(
resolveColor(requireContext(), android.R.attr.textColorSecondary) textColorSecondary()
) )
binding.breadCrumbs.setCallback(this) binding.breadCrumbs.setCallback(this)
} }
private fun setUpRecyclerView() { private fun setUpRecyclerView() {
binding.recyclerView.layoutManager = LinearLayoutManager( binding.recyclerView.layoutManager = LinearLayoutManager(activity)
activity create(
)
val fastScroller = create(
binding.recyclerView binding.recyclerView
) )
binding.recyclerView.setOnApplyWindowInsetsListener(
ScrollingViewOnApplyWindowInsetsListener(binding.recyclerView, fastScroller)
)
} }
private fun toList(file: File): ArrayList<File> { private fun toList(file: File): ArrayList<File> {

View file

@ -27,12 +27,10 @@ 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.extensions.navigate
import code.name.monkey.retromusic.fragments.ReloadType 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 code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil
import com.google.android.gms.cast.framework.CastButtonFactory import com.google.android.gms.cast.framework.CastButtonFactory
import com.google.android.material.transition.MaterialSharedAxis import com.google.android.material.transition.MaterialSharedAxis

View file

@ -23,6 +23,7 @@ import android.view.View
import androidx.activity.addCallback import androidx.activity.addCallback
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import androidx.core.view.doOnLayout
import androidx.core.view.doOnPreDraw 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
@ -37,9 +38,11 @@ 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.extensions.accentColor import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.drawNextToNavbar import code.name.monkey.retromusic.extensions.drawNextToNavbar
import code.name.monkey.retromusic.extensions.elevatedAccentColor
import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import code.name.monkey.retromusic.glide.GlideApp import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.interfaces.IScrollHelper
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import com.google.android.gms.cast.framework.CastButtonFactory import com.google.android.gms.cast.framework.CastButtonFactory
import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.MaterialShapeDrawable
@ -47,7 +50,8 @@ import com.google.android.material.transition.MaterialFadeThrough
import com.google.android.material.transition.MaterialSharedAxis import com.google.android.material.transition.MaterialSharedAxis
class HomeFragment : class HomeFragment :
AbsMainActivityFragment(if (PreferenceUtil.isHomeBanner) R.layout.fragment_banner_home else R.layout.fragment_home) { AbsMainActivityFragment(if (PreferenceUtil.isHomeBanner) R.layout.fragment_banner_home else R.layout.fragment_home),
IScrollHelper {
private var _binding: HomeBindingAdapter? = null private var _binding: HomeBindingAdapter? = null
private val binding get() = _binding!! private val binding get() = _binding!!
@ -60,9 +64,8 @@ class HomeFragment :
setupListeners() setupListeners()
binding.titleWelcome.text = String.format("%s", PreferenceUtil.userName) binding.titleWelcome.text = String.format("%s", PreferenceUtil.userName)
enterTransition = MaterialFadeThrough().apply { enterTransition = MaterialFadeThrough().addTarget(binding.contentContainer)
addTarget(binding.contentContainer) reenterTransition = MaterialFadeThrough().addTarget(binding.contentContainer)
}
val homeAdapter = HomeAdapter(mainActivity) val homeAdapter = HomeAdapter(mainActivity)
binding.recyclerView.apply { binding.recyclerView.apply {
@ -75,6 +78,7 @@ class HomeFragment :
loadProfile() loadProfile()
setupTitle() setupTitle()
colorButtons()
postponeEnterTransition() postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() } view.doOnPreDraw { startPostponedEnterTransition() }
binding.appBarLayout.statusBarForeground = binding.appBarLayout.statusBarForeground =
@ -84,6 +88,21 @@ class HomeFragment :
remove() remove()
mainActivity.finish() mainActivity.finish()
} }
view.doOnLayout {
adjustPlaylistButtons()
}
}
private fun adjustPlaylistButtons() {
val buttons =
listOf(binding.history, binding.lastAdded, binding.topPlayed, binding.actionShuffle)
buttons.maxOf { it.lineCount }.let { maxLineCount->
buttons.forEach { button ->
// Set the highest line count to every button for consistency
button.setLines(maxLineCount)
}
}
} }
private fun setupListeners() { private fun setupListeners() {
@ -172,6 +191,13 @@ class HomeFragment :
.into(binding.userImage) .into(binding.userImage)
} }
fun colorButtons() {
binding.history.elevatedAccentColor()
binding.lastAdded.elevatedAccentColor()
binding.topPlayed.elevatedAccentColor()
binding.actionShuffle.elevatedAccentColor()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater) super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.menu_main, menu) inflater.inflate(R.menu.menu_main, menu)
@ -189,7 +215,7 @@ class HomeFragment :
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.action_cast) CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.action_cast)
} }
fun scrollToTop() { override fun scrollToTop() {
binding.container.scrollTo(0, 0) binding.container.scrollTo(0, 0)
binding.appBarLayout.setExpanded(true) binding.appBarLayout.setExpanded(true)
} }

View file

@ -0,0 +1,174 @@
package code.name.monkey.retromusic.fragments.other
import android.content.SharedPreferences
import android.os.Bundle
import android.text.TextUtils
import android.view.View
import android.widget.FrameLayout
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.SHOW_LYRICS
import code.name.monkey.retromusic.databinding.FragmentCoverLyricsBinding
import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.model.lyrics.AbsSynchronizedLyrics
import code.name.monkey.retromusic.model.lyrics.Lyrics
import code.name.monkey.retromusic.util.LyricUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.jaudiotagger.audio.exceptions.CannotReadException
import java.io.File
import java.io.FileNotFoundException
class CoverLyricsFragment : AbsMusicServiceFragment(R.layout.fragment_cover_lyrics),
MusicProgressViewUpdateHelper.Callback, SharedPreferences.OnSharedPreferenceChangeListener {
private var progressViewUpdateHelper: MusicProgressViewUpdateHelper? = null
private var _binding: FragmentCoverLyricsBinding? = null
private val binding get() = _binding!!
private val lyricsLayout: FrameLayout get() = binding.playerLyrics
private val lyricsLine1: TextView get() = binding.playerLyricsLine1
private val lyricsLine2: TextView get() = binding.playerLyricsLine2
private var lyrics: Lyrics? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentCoverLyricsBinding.bind(view)
progressViewUpdateHelper = MusicProgressViewUpdateHelper(this, 500, 1000)
if (PreferenceUtil.showLyrics) {
progressViewUpdateHelper?.start()
}
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (key == SHOW_LYRICS) {
if (sharedPreferences?.getBoolean(key, false) == true) {
progressViewUpdateHelper?.start()
binding.root.isVisible = true
updateLyrics()
} else {
progressViewUpdateHelper?.stop()
binding.root.isVisible = false
}
}
}
override fun onPlayingMetaChanged() {
super.onPlayingMetaChanged()
if (PreferenceUtil.showLyrics) {
updateLyrics()
}
}
override fun onServiceConnected() {
super.onServiceConnected()
if (PreferenceUtil.showLyrics) {
updateLyrics()
}
}
private fun updateLyrics() {
lyrics = null
lifecycleScope.launch(Dispatchers.IO) {
val song = MusicPlayerRemote.currentSong
lyrics = try {
val lrcFile: File? = LyricUtil.getSyncedLyricsFile(song)
val data: String = LyricUtil.getStringFromLrc(lrcFile)
Lyrics.parse(song,
if (!TextUtils.isEmpty(data)) {
data
} else {
// Get Embedded Lyrics
LyricUtil.getEmbeddedSyncedLyrics(song.data)
}
)
} catch (err: FileNotFoundException) {
null
} catch (e: CannotReadException) {
null
}
}
}
override fun onUpdateProgressViews(progress: Int, total: Int) {
if (_binding == null) return
if (!isLyricsLayoutVisible()) {
hideLyricsLayout()
return
}
if (lyrics !is AbsSynchronizedLyrics) return
val synchronizedLyrics = lyrics as AbsSynchronizedLyrics
lyricsLayout.visibility = View.VISIBLE
lyricsLayout.alpha = 1f
val oldLine = lyricsLine2.text.toString()
val line = synchronizedLyrics.getLine(progress)
if (oldLine != line || oldLine.isEmpty()) {
lyricsLine1.text = oldLine
lyricsLine2.text = line
lyricsLine1.isVisible = true
lyricsLine2.isVisible = true
lyricsLine2.measure(
View.MeasureSpec.makeMeasureSpec(
lyricsLine2.measuredWidth,
View.MeasureSpec.EXACTLY
),
View.MeasureSpec.UNSPECIFIED
)
val h: Float = lyricsLine2.measuredHeight.toFloat()
lyricsLine1.alpha = 1f
lyricsLine1.translationY = 0f
lyricsLine1.animate().alpha(0f).translationY(-h).duration =
AbsPlayerFragment.VISIBILITY_ANIM_DURATION
lyricsLine2.alpha = 0f
lyricsLine2.translationY = h
lyricsLine2.animate().alpha(1f).translationY(0f).duration =
AbsPlayerFragment.VISIBILITY_ANIM_DURATION
}
}
private fun isLyricsLayoutVisible(): Boolean {
return lyrics != null && lyrics!!.isSynchronized && lyrics!!.isValid
}
private fun hideLyricsLayout() {
lyricsLayout.animate().alpha(0f).setDuration(AbsPlayerFragment.VISIBILITY_ANIM_DURATION)
.withEndAction {
if (_binding == null) return@withEndAction
lyricsLayout.isVisible = false
lyricsLine1.text = null
lyricsLine2.text = null
}
}
override fun onResume() {
super.onResume()
PreferenceManager.getDefaultSharedPreferences(requireContext())
.registerOnSharedPreferenceChangeListener(this)
}
override fun onDestroyView() {
super.onDestroyView()
PreferenceManager.getDefaultSharedPreferences(requireContext())
.unregisterOnSharedPreferenceChangeListener(this)
progressViewUpdateHelper?.stop()
_binding = null
}
}

View file

@ -64,14 +64,6 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
returnTransition = MaterialSharedAxis(MaterialSharedAxis.Y, false) returnTransition = MaterialSharedAxis(MaterialSharedAxis.Y, false)
} }
} }
binding.appBarLayout.statusBarForeground =
MaterialShapeDrawable.createWithElevationOverlay(requireContext())
postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() }
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
mainActivity.setSupportActionBar(binding.toolbar) mainActivity.setSupportActionBar(binding.toolbar)
binding.progressIndicator.hide() binding.progressIndicator.hide()
when (args.type) { when (args.type) {
@ -92,6 +84,10 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
binding.recyclerView.updatePadding(bottom = height.toInt()) binding.recyclerView.updatePadding(bottom = height.toInt())
} }
}) })
binding.appBarLayout.statusBarForeground =
MaterialShapeDrawable.createWithElevationOverlay(requireContext())
postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() }
} }
private fun lastAddedSongs() { private fun lastAddedSongs() {
@ -104,6 +100,7 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
binding.recyclerView.apply { binding.recyclerView.apply {
adapter = songAdapter adapter = songAdapter
layoutManager = linearLayoutManager() layoutManager = linearLayoutManager()
scheduleLayoutAnimation()
} }
libraryViewModel.recentSongs().observe(viewLifecycleOwner, { songs -> libraryViewModel.recentSongs().observe(viewLifecycleOwner, { songs ->
songAdapter.swapDataSet(songs) songAdapter.swapDataSet(songs)

View file

@ -231,13 +231,7 @@ class LyricsFragment : AbsMusicServiceFragment(R.layout.fragment_lyrics) {
@SuppressLint("CheckResult") @SuppressLint("CheckResult")
private fun editSyncedLyrics() { private fun editSyncedLyrics() {
var lrcFile: File? = null val content: String = LyricUtil.getStringFromLrc(LyricUtil.getSyncedLyricsFile(song))
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(requireContext(), BottomSheet(LayoutMode.WRAP_CONTENT)).show { MaterialDialog(requireContext(), BottomSheet(LayoutMode.WRAP_CONTENT)).show {
title(res = R.string.edit_synced_lyrics) title(res = R.string.edit_synced_lyrics)
@ -332,11 +326,8 @@ class LyricsFragment : AbsMusicServiceFragment(R.layout.fragment_lyrics) {
fun loadLRCLyrics() { fun loadLRCLyrics() {
binding.lyricsView.setLabel("Empty") binding.lyricsView.setLabel("Empty")
val song = MusicPlayerRemote.currentSong LyricUtil.getSyncedLyricsFile(MusicPlayerRemote.currentSong)?.let {
if (LyricUtil.isLrcOriginalFileExist(song.data)) { binding.lyricsView.loadLrc(it)
binding.lyricsView.loadLrc(LyricUtil.getLocalLyricOriginalFile(song.data))
} else if (LyricUtil.isLrcFileExist(song.title, song.artistName)) {
binding.lyricsView.loadLrc(LyricUtil.getLocalLyricFile(song.title, song.artistName))
} }
} }

View file

@ -32,6 +32,8 @@ 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
@ -110,13 +112,13 @@ open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_p
} }
private fun updateSongCover() { private fun updateSongCover() {
// val song = MusicPlayerRemote.currentSong val song = MusicPlayerRemote.currentSong
// GlideApp.with(requireContext()) GlideApp.with(requireContext())
// .asBitmap() .asBitmap()
// .songCoverOptions(song) .songCoverOptions(song)
// .transition(RetroGlideExtension.getDefaultTransition()) .transition(RetroGlideExtension.getDefaultTransition())
// .load(RetroGlideExtension.getSongModel(song)) .load(RetroGlideExtension.getSongModel(song))
// .into(binding.image) .into(binding.image)
} }
override fun onServiceConnected() { override fun onServiceConnected() {

View file

@ -16,7 +16,6 @@ package code.name.monkey.retromusic.fragments.other
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
import android.net.Uri import android.net.Uri
@ -31,8 +30,6 @@ import androidx.core.view.doOnPreDraw
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.Constants.USER_BANNER import code.name.monkey.retromusic.Constants.USER_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.R
@ -87,6 +84,7 @@ class UserInfoFragment : Fragment() {
applyToolbar(binding.toolbar) applyToolbar(binding.toolbar)
binding.nameContainer.accentColor() binding.nameContainer.accentColor()
binding.next.accentColor()
binding.name.setText(PreferenceUtil.userName) binding.name.setText(PreferenceUtil.userName)
binding.userImage.setOnClickListener { binding.userImage.setOnClickListener {
@ -111,14 +109,6 @@ class UserInfoFragment : Fragment() {
findNavController().navigateUp() findNavController().navigateUp()
} }
val textColor =
MaterialValueHelper.getPrimaryTextColor(
requireContext(),
ColorUtil.isColorLight(accentColor())
)
binding.next.backgroundTintList = ColorStateList.valueOf(accentColor())
binding.next.iconTint = ColorStateList.valueOf(textColor)
binding.next.setTextColor(textColor)
loadProfile() loadProfile()
postponeEnterTransition() postponeEnterTransition()
view.doOnPreDraw { view.doOnPreDraw {
@ -134,12 +124,11 @@ class UserInfoFragment : Fragment() {
private fun loadProfile() { private fun loadProfile() {
binding.bannerImage.let { binding.bannerImage.let {
GlideApp.with(this) GlideApp.with(this)
.asBitmap()
.load(RetroGlideExtension.getBannerModel()) .load(RetroGlideExtension.getBannerModel())
.profileBannerOptions(RetroGlideExtension.getBannerModel()) .profileBannerOptions(RetroGlideExtension.getBannerModel())
.into(it) .into(it)
} }
GlideApp.with(this).asBitmap() GlideApp.with(this)
.load(RetroGlideExtension.getUserModel()) .load(RetroGlideExtension.getUserModel())
.userProfileOptions(RetroGlideExtension.getUserModel()) .userProfileOptions(RetroGlideExtension.getUserModel())
.into(binding.userImage) .into(binding.userImage)
@ -217,7 +206,7 @@ class UserInfoFragment : Fragment() {
val appDir = requireContext().filesDir val appDir = requireContext().filesDir
val file = File(appDir, fileName) val file = File(appDir, fileName)
var successful = false var successful = false
kotlin.runCatching { runCatching {
val os = BufferedOutputStream(FileOutputStream(file)) val os = BufferedOutputStream(FileOutputStream(file))
successful = ImageUtil.resizeBitmap(bitmap, 2048) successful = ImageUtil.resizeBitmap(bitmap, 2048)
.compress(Bitmap.CompressFormat.WEBP, 100, os) .compress(Bitmap.CompressFormat.WEBP, 100, os)

View file

@ -14,28 +14,30 @@
*/ */
package code.name.monkey.retromusic.fragments.player package code.name.monkey.retromusic.fragments.player
import android.annotation.SuppressLint
import android.content.SharedPreferences import android.content.SharedPreferences
import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils
import android.view.View import android.view.View
import android.widget.FrameLayout import androidx.core.view.isInvisible
import android.widget.TextView
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.SHOW_LYRICS import code.name.monkey.retromusic.SHOW_LYRICS
import code.name.monkey.retromusic.adapter.album.AlbumCoverPagerAdapter import code.name.monkey.retromusic.adapter.album.AlbumCoverPagerAdapter
import code.name.monkey.retromusic.adapter.album.AlbumCoverPagerAdapter.AlbumCoverFragment import code.name.monkey.retromusic.adapter.album.AlbumCoverPagerAdapter.AlbumCoverFragment
import code.name.monkey.retromusic.databinding.FragmentPlayerAlbumCoverBinding import code.name.monkey.retromusic.databinding.FragmentPlayerAlbumCoverBinding
import code.name.monkey.retromusic.extensions.isColorLight
import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.fragments.NowPlayingScreen.* import code.name.monkey.retromusic.fragments.NowPlayingScreen.*
import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.base.goToLyrics
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.model.lyrics.AbsSynchronizedLyrics import code.name.monkey.retromusic.lyrics.CoverLrcView
import code.name.monkey.retromusic.model.lyrics.Lyrics import code.name.monkey.retromusic.model.lyrics.Lyrics
import code.name.monkey.retromusic.transform.CarousalPagerTransformer import code.name.monkey.retromusic.transform.CarousalPagerTransformer
import code.name.monkey.retromusic.transform.ParallaxPagerTransformer import code.name.monkey.retromusic.transform.ParallaxPagerTransformer
@ -45,11 +47,6 @@ import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
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 org.jaudiotagger.audio.AudioFileIO
import org.jaudiotagger.audio.exceptions.CannotReadException
import org.jaudiotagger.tag.FieldKey
import java.io.File
import java.io.FileNotFoundException
class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_player_album_cover), class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_player_album_cover),
ViewPager.OnPageChangeListener, MusicProgressViewUpdateHelper.Callback, ViewPager.OnPageChangeListener, MusicProgressViewUpdateHelper.Callback,
@ -70,9 +67,7 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
} }
private var progressViewUpdateHelper: MusicProgressViewUpdateHelper? = null private var progressViewUpdateHelper: MusicProgressViewUpdateHelper? = null
private val lyricsLayout: FrameLayout get() = binding.playerLyrics private val lrcView: CoverLrcView get() = binding.lyricsView
private val lyricsLine1: TextView get() = binding.playerLyricsLine1
private val lyricsLine2: TextView get() = binding.playerLyricsLine2
var lyrics: Lyrics? = null var lyrics: Lyrics? = null
@ -82,102 +77,33 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
} }
private fun updateLyrics() { private fun updateLyrics() {
lyrics = null binding.lyricsView.setLabel(context?.getString(R.string.no_lyrics_found))
lifecycleScope.launch(Dispatchers.IO) {
val song = MusicPlayerRemote.currentSong val song = MusicPlayerRemote.currentSong
val lyrics = try { lifecycleScope.launch(Dispatchers.IO) {
var lrcFile: File? = null val lrcFile = LyricUtil.getSyncedLyricsFile(song)
if (LyricUtil.isLrcOriginalFileExist(song.data)) { if (lrcFile != null) {
lrcFile = LyricUtil.getLocalLyricOriginalFile(song.data)
} else if (LyricUtil.isLrcFileExist(song.title, song.artistName)) {
lrcFile = LyricUtil.getLocalLyricFile(song.title, song.artistName)
}
val data: String = LyricUtil.getStringFromLrc(lrcFile)
if (!TextUtils.isEmpty(data)) {
Lyrics.parse(song, data)
} else {
// Get Embedded Lyrics and check if they are Synchronized
val embeddedLyrics = try{
AudioFileIO.read(File(song.data)).tagOrCreateDefault.getFirst(FieldKey.LYRICS)
} catch(e: Exception){
null
}
if (AbsSynchronizedLyrics.isSynchronized(embeddedLyrics)) {
Lyrics.parse(song, embeddedLyrics)
} else {
null
}
}
} catch (err: FileNotFoundException) {
null
} catch (e: CannotReadException){
null
}
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
this@PlayerAlbumCoverFragment.lyrics = lyrics binding.lyricsView.loadLrc(lrcFile)
}
} else {
val embeddedLyrics = LyricUtil.getEmbeddedSyncedLyrics(song.data)
withContext(Dispatchers.Main) {
if (embeddedLyrics != null) {
binding.lyricsView.loadLrc(embeddedLyrics)
} else {
binding.lyricsView.reset()
} }
} }
} }
}
}
override fun onUpdateProgressViews(progress: Int, total: Int) { override fun onUpdateProgressViews(progress: Int, total: Int) {
if (_binding == null) return binding.lyricsView.updateTime(progress.toLong())
if (!isLyricsLayoutVisible()) {
hideLyricsLayout()
return
}
if (lyrics !is AbsSynchronizedLyrics) return
val synchronizedLyrics = lyrics as AbsSynchronizedLyrics
lyricsLayout.visibility = View.VISIBLE
lyricsLayout.alpha = 1f
val oldLine = lyricsLine2.text.toString()
val line = synchronizedLyrics.getLine(progress)
if (oldLine != line || oldLine.isEmpty()) {
lyricsLine1.text = oldLine
lyricsLine2.text = line
lyricsLine1.visibility = View.VISIBLE
lyricsLine2.visibility = View.VISIBLE
lyricsLine2.measure(
View.MeasureSpec.makeMeasureSpec(
lyricsLine2.measuredWidth,
View.MeasureSpec.EXACTLY
),
View.MeasureSpec.UNSPECIFIED
)
val h: Float = lyricsLine2.measuredHeight.toFloat()
lyricsLine1.alpha = 1f
lyricsLine1.translationY = 0f
lyricsLine1.animate().alpha(0f).translationY(-h).duration =
AbsPlayerFragment.VISIBILITY_ANIM_DURATION
lyricsLine2.alpha = 0f
lyricsLine2.translationY = h
lyricsLine2.animate().alpha(1f).translationY(0f).duration =
AbsPlayerFragment.VISIBILITY_ANIM_DURATION
}
}
private fun isLyricsLayoutVisible(): Boolean {
return lyrics != null && lyrics!!.isSynchronized && lyrics!!.isValid
}
private fun hideLyricsLayout() {
lyricsLayout.animate().alpha(0f).setDuration(AbsPlayerFragment.VISIBILITY_ANIM_DURATION)
.withEndAction {
if (_binding == null) return@withEndAction
lyricsLayout.visibility = View.GONE
lyricsLine1.text = null
lyricsLine2.text = null
}
} }
@SuppressLint("ClickableViewAccessibility")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
_binding = FragmentPlayerAlbumCoverBinding.bind(view) _binding = FragmentPlayerAlbumCoverBinding.bind(view)
@ -208,27 +134,34 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
) )
} }
progressViewUpdateHelper = MusicProgressViewUpdateHelper(this, 500, 1000) progressViewUpdateHelper = MusicProgressViewUpdateHelper(this, 500, 1000)
// Don't show lyrics container for below conditions maybeInitLyrics()
if (!(nps == Circle || nps == Peak || nps == Tiny || !PreferenceUtil.showLyrics)) { lrcView.apply {
progressViewUpdateHelper?.start() setDraggable(true) { time ->
MusicPlayerRemote.seekTo(time.toInt())
MusicPlayerRemote.resumePlaying()
true
}
setOnFlingXListener { velocityX ->
when {
velocityX < 0 -> {
MusicPlayerRemote.playNextSong()
true
}
velocityX > 0 -> {
MusicPlayerRemote.playPreviousSong()
true
}
else -> {
false
}
}
} }
// Go to lyrics activity when clicked lyrics
binding.playerLyricsLine2.setOnClickListener {
goToLyrics(requireActivity())
} }
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
val nps = PreferenceUtil.nowPlayingScreen maybeInitLyrics()
// Don't show lyrics container for below conditions
if (nps == Circle || nps == Peak || nps == Tiny || !PreferenceUtil.showLyrics) {
lyricsLayout.isVisible = false
progressViewUpdateHelper?.stop()
} else {
lyricsLayout.isVisible = true
progressViewUpdateHelper?.start()
}
PreferenceManager.getDefaultSharedPreferences(requireContext()) PreferenceManager.getDefaultSharedPreferences(requireContext())
.registerOnSharedPreferenceChangeListener(this) .registerOnSharedPreferenceChangeListener(this)
} }
@ -259,22 +192,48 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) { override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
if (key == SHOW_LYRICS) { if (key == SHOW_LYRICS) {
if (sharedPreferences.getBoolean(key, false)) { if (sharedPreferences.getBoolean(key, false)) {
progressViewUpdateHelper?.start() maybeInitLyrics()
lyricsLayout.animate().alpha(1f).duration =
AbsPlayerFragment.VISIBILITY_ANIM_DURATION
binding.playerLyrics.isVisible = true
} else { } else {
showLyrics(false)
progressViewUpdateHelper?.stop() progressViewUpdateHelper?.stop()
lyricsLayout.animate().alpha(0f)
.setDuration(AbsPlayerFragment.VISIBILITY_ANIM_DURATION)
.withEndAction {
if (_binding != null) {
binding.playerLyrics.isVisible = false
lyricsLine1.text = null
lyricsLine2.text = null
} }
} }
} }
private fun setLRCViewColors(backgroundColor: Int) {
val primaryColor = MaterialValueHelper.getPrimaryTextColor(
requireContext(),
backgroundColor.isColorLight
)
val secondaryColor = MaterialValueHelper.getSecondaryDisabledTextColor(
requireContext(),
backgroundColor.isColorLight
)
lrcView.apply {
setCurrentColor(primaryColor)
setTimeTextColor(primaryColor)
setTimelineColor(primaryColor)
setNormalColor(secondaryColor)
setTimelineTextColor(primaryColor)
}
}
private fun showLyrics(visible: Boolean) {
lrcView.isVisible = visible
viewPager.isInvisible = visible
}
private fun maybeInitLyrics() {
val nps = PreferenceUtil.nowPlayingScreen
// Don't show lyrics container for below conditions
if (lyricViewNpsList.contains(nps) && PreferenceUtil.showLyrics) {
showLyrics(true)
progressViewUpdateHelper?.start()
lrcView.animate().alpha(1f).duration =
AbsPlayerFragment.VISIBILITY_ANIM_DURATION
} else {
showLyrics(false)
progressViewUpdateHelper?.stop()
} }
} }
@ -309,6 +268,19 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
private fun notifyColorChange(color: MediaNotificationProcessor) { private fun notifyColorChange(color: MediaNotificationProcessor) {
callbacks?.onColorChanged(color) callbacks?.onColorChanged(color)
setLRCViewColors(
when (PreferenceUtil.nowPlayingScreen) {
Adaptive, Fit, Plain, Simple -> surfaceColor()
Flat, Normal -> if (PreferenceUtil.isAdaptiveColor) {
color.backgroundColor
} else {
surfaceColor()
}
Color -> color.backgroundColor
Blur -> Color.BLACK
else -> color.backgroundColor
}
)
} }
fun setCallbacks(listener: Callbacks) { fun setCallbacks(listener: Callbacks) {
@ -325,4 +297,7 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
companion object { companion object {
val TAG: String = PlayerAlbumCoverFragment::class.java.simpleName val TAG: String = PlayerAlbumCoverFragment::class.java.simpleName
} }
private val lyricViewNpsList =
listOf(Blur, Classic, Color, Flat, Material, Normal, Plain, Simple)
} }

View file

@ -17,14 +17,10 @@ package code.name.monkey.retromusic.fragments.player.adaptive
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
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.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentAdaptivePlayerBinding import code.name.monkey.retromusic.databinding.FragmentAdaptivePlayerBinding
import code.name.monkey.retromusic.extensions.drawAboveSystemBars import code.name.monkey.retromusic.extensions.*
import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.extensions.textColorPrimary
import code.name.monkey.retromusic.extensions.textColorSecondary
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
@ -47,7 +43,7 @@ class AdaptiveFragment : AbsPlayerFragment(R.layout.fragment_adaptive_player) {
_binding = FragmentAdaptivePlayerBinding.bind(view) _binding = FragmentAdaptivePlayerBinding.bind(view)
setUpSubFragments() setUpSubFragments()
setUpPlayerToolbar() setUpPlayerToolbar()
binding.root.drawAboveSystemBars() binding.playbackControlsFragment.drawAboveSystemBars()
} }
private fun setUpSubFragments() { private fun setUpSubFragments() {
@ -108,7 +104,7 @@ class AdaptiveFragment : AbsPlayerFragment(R.layout.fragment_adaptive_player) {
libraryViewModel.updateColor(color.primaryTextColor) libraryViewModel.updateColor(color.primaryTextColor)
ToolbarContentTintHelper.colorizeToolbar( ToolbarContentTintHelper.colorizeToolbar(
binding.playerToolbar, binding.playerToolbar,
ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal), colorControlNormal(),
requireActivity() requireActivity()
) )
} }
@ -130,7 +126,7 @@ class AdaptiveFragment : AbsPlayerFragment(R.layout.fragment_adaptive_player) {
} }
override fun toolbarIconColor(): Int { override fun toolbarIconColor(): Int {
return ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal) return colorControlNormal()
} }
override val paletteColor: Int override val paletteColor: Int

View file

@ -16,6 +16,8 @@ package code.name.monkey.retromusic.fragments.player.blur
import android.content.SharedPreferences import android.content.SharedPreferences
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
@ -27,18 +29,18 @@ import code.name.monkey.retromusic.databinding.FragmentBlurBinding
import code.name.monkey.retromusic.extensions.drawAboveSystemBars import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment
import code.name.monkey.retromusic.glide.BlurTransformation import code.name.monkey.retromusic.glide.*
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.PreferenceUtil.blurAmount import code.name.monkey.retromusic.util.PreferenceUtil.blurAmount
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
class BlurPlayerFragment : AbsPlayerFragment(R.layout.fragment_blur), class BlurPlayerFragment : AbsPlayerFragment(R.layout.fragment_blur),
SharedPreferences.OnSharedPreferenceChangeListener { SharedPreferences.OnSharedPreferenceChangeListener {
private var lastRequest: GlideRequest<Drawable>? = null
override fun playerToolbar(): Toolbar { override fun playerToolbar(): Toolbar {
return binding.playerToolbar return binding.playerToolbar
} }
@ -111,24 +113,21 @@ class BlurPlayerFragment : AbsPlayerFragment(R.layout.fragment_blur),
get() = lastColor get() = lastColor
private fun updateBlur() { private fun updateBlur() {
binding.colorBackground.clearColorFilter() // https://github.com/bumptech/glide/issues/527#issuecomment-148840717
GlideApp.with(requireActivity()).asBitmapPalette() GlideApp.with(this)
.songCoverOptions(MusicPlayerRemote.currentSong)
.load(RetroGlideExtension.getSongModel(MusicPlayerRemote.currentSong)) .load(RetroGlideExtension.getSongModel(MusicPlayerRemote.currentSong))
.dontAnimate() .simpleSongCoverOptions(MusicPlayerRemote.currentSong)
.transform( .transform(
BlurTransformation.Builder(requireContext()) BlurTransformation.Builder(requireContext()).blurRadius(blurAmount.toFloat())
.blurRadius(blurAmount.toFloat())
.build() .build()
) ).thumbnail(lastRequest)
.into(object : RetroMusicColoredTarget(binding.colorBackground) { .error(GlideApp.with(this).load(ColorDrawable(Color.DKGRAY)).fitCenter())
override fun onColorReady(colors: MediaNotificationProcessor) { .also {
if (colors.backgroundColor == defaultFooterColor) { lastRequest = it.clone()
binding.colorBackground.setColorFilter(colors.backgroundColor) it.crossfadeListener()
.into(binding.colorBackground)
} }
} }
})
}
override fun onServiceConnected() { override fun onServiceConnected() {
updateIsFavorite() updateIsFavorite()

View file

@ -16,6 +16,7 @@ package code.name.monkey.retromusic.fragments.player.cardblur
import android.content.SharedPreferences import android.content.SharedPreferences
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.Drawable
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
@ -28,10 +29,7 @@ import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment
import code.name.monkey.retromusic.fragments.player.normal.PlayerFragment import code.name.monkey.retromusic.fragments.player.normal.PlayerFragment
import code.name.monkey.retromusic.glide.BlurTransformation import code.name.monkey.retromusic.glide.*
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.PreferenceUtil.blurAmount import code.name.monkey.retromusic.util.PreferenceUtil.blurAmount
@ -50,6 +48,7 @@ class CardBlurFragment : AbsPlayerFragment(R.layout.fragment_card_blur_player),
private var _binding: FragmentCardBlurPlayerBinding? = null private var _binding: FragmentCardBlurPlayerBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
private var lastRequest: GlideRequest<Drawable>? = null
override fun onShow() { override fun onShow() {
playbackControlsFragment.show() playbackControlsFragment.show()
@ -136,23 +135,20 @@ class CardBlurFragment : AbsPlayerFragment(R.layout.fragment_card_blur_player),
} }
private fun updateBlur() { private fun updateBlur() {
binding.colorBackground.clearColorFilter() // https://github.com/bumptech/glide/issues/527#issuecomment-148840717
GlideApp.with(requireActivity()).asBitmapPalette() GlideApp.with(this)
.songCoverOptions(MusicPlayerRemote.currentSong)
.load(RetroGlideExtension.getSongModel(MusicPlayerRemote.currentSong)) .load(RetroGlideExtension.getSongModel(MusicPlayerRemote.currentSong))
.dontAnimate() .simpleSongCoverOptions(MusicPlayerRemote.currentSong)
.transform( .transform(
BlurTransformation.Builder(requireContext()).blurRadius(blurAmount.toFloat()) BlurTransformation.Builder(requireContext()).blurRadius(blurAmount.toFloat())
.build() .build()
) )
.into(object : RetroMusicColoredTarget(binding.colorBackground) { .thumbnail(lastRequest).also {
override fun onColorReady(colors: MediaNotificationProcessor) { lastRequest = it.clone()
if (colors.backgroundColor == defaultFooterColor) { it.crossfadeListener()
binding.colorBackground.setColorFilter(colors.backgroundColor) .into(binding.colorBackground)
} }
} }
})
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()

View file

@ -18,19 +18,19 @@ import android.animation.ObjectAnimator
import android.content.Context import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.media.AudioManager import android.media.AudioManager
import android.os.Bundle import android.os.Bundle
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 android.view.animation.Animation
import android.view.animation.LinearInterpolator import android.view.animation.LinearInterpolator
import android.widget.SeekBar import android.widget.SeekBar
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
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.*
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentCirclePlayerBinding import code.name.monkey.retromusic.databinding.FragmentCirclePlayerBinding
import code.name.monkey.retromusic.extensions.* import code.name.monkey.retromusic.extensions.*
@ -38,6 +38,7 @@ import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.base.goToAlbum import code.name.monkey.retromusic.fragments.base.goToAlbum
import code.name.monkey.retromusic.fragments.base.goToArtist import code.name.monkey.retromusic.fragments.base.goToArtist
import code.name.monkey.retromusic.glide.*
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
@ -47,10 +48,9 @@ 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.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 code.name.monkey.retromusic.views.SeekArc
import code.name.monkey.retromusic.views.SeekArc.OnSeekArcChangeListener
import code.name.monkey.retromusic.volume.AudioVolumeObserver import code.name.monkey.retromusic.volume.AudioVolumeObserver
import code.name.monkey.retromusic.volume.OnAudioVolumeChangedListener import code.name.monkey.retromusic.volume.OnAudioVolumeChangedListener
import me.tankery.lib.circularseekbar.CircularSeekBar
/** /**
* Created by hemanths on 2020-01-06. * Created by hemanths on 2020-01-06.
@ -58,7 +58,7 @@ import code.name.monkey.retromusic.volume.OnAudioVolumeChangedListener
class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player), Callback, class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player), Callback,
OnAudioVolumeChangedListener, OnAudioVolumeChangedListener,
OnSeekArcChangeListener { CircularSeekBar.OnCircularSeekBarChangeListener {
private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper
private var audioVolumeObserver: AudioVolumeObserver? = null private var audioVolumeObserver: AudioVolumeObserver? = null
@ -69,6 +69,9 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
private var _binding: FragmentCirclePlayerBinding? = null private var _binding: FragmentCirclePlayerBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
private var rotateAnimator: ObjectAnimator? = null
private var lastRequest: GlideRequest<Drawable>? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
progressViewUpdateHelper = MusicProgressViewUpdateHelper(this) progressViewUpdateHelper = MusicProgressViewUpdateHelper(this)
@ -103,7 +106,7 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
setOnMenuItemClickListener(this@CirclePlayerFragment) setOnMenuItemClickListener(this@CirclePlayerFragment)
ToolbarContentTintHelper.colorizeToolbar( ToolbarContentTintHelper.colorizeToolbar(
this, this,
ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal), colorControlNormal(),
requireActivity() requireActivity()
) )
} }
@ -116,11 +119,17 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
ThemeStore.accentColor(requireContext()), ThemeStore.accentColor(requireContext()),
false false
) )
binding.volumeSeekBar.progressColor = accentColor() binding.volumeSeekBar.circleProgressColor = accentColor()
binding.volumeSeekBar.arcColor = ColorUtil.withAlpha(accentColor(), 0.25f) binding.volumeSeekBar.circleColor = ColorUtil.withAlpha(accentColor(), 0.25f)
setUpPlayPauseFab() setUpPlayPauseFab()
setUpPrevNext() setUpPrevNext()
setUpPlayerToolbar() setUpPlayerToolbar()
binding.albumCoverOverlay.background = ColorDrawable(
MaterialValueHelper.getPrimaryTextColor(
requireContext(),
accentColor().isColorLight
)
)
} }
private fun setUpPrevNext() { private fun setUpPrevNext() {
@ -144,6 +153,17 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
binding.playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) binding.playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler())
} }
private fun setupRotateAnimation() {
rotateAnimator = ObjectAnimator.ofFloat(binding.albumCover, View.ROTATION, 360F).apply {
interpolator = LinearInterpolator()
repeatCount = Animation.INFINITE
duration = 10000
if (MusicPlayerRemote.isPlaying) {
start()
}
}
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
progressViewUpdateHelper.start() progressViewUpdateHelper.start()
@ -153,11 +173,11 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
audioVolumeObserver?.register(AudioManager.STREAM_MUSIC, this) audioVolumeObserver?.register(AudioManager.STREAM_MUSIC, this)
val audioManager = audioManager val audioManager = audioManager
if (audioManager != null) { binding.volumeSeekBar.max =
binding.volumeSeekBar.max = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC).toFloat()
binding.volumeSeekBar.progress = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) binding.volumeSeekBar.progress =
} audioManager.getStreamVolume(AudioManager.STREAM_MUSIC).toFloat()
binding.volumeSeekBar.setOnSeekArcChangeListener(this) binding.volumeSeekBar.setOnSeekBarChangeListener(this)
} }
override fun onPause() { override fun onPause() {
@ -178,7 +198,7 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
override fun onBackPressed(): Boolean = false override fun onBackPressed(): Boolean = false
override fun toolbarIconColor(): Int = override fun toolbarIconColor(): Int =
ATHUtil.resolveColor(requireContext(), android.R.attr.colorControlNormal) colorControlNormal()
override val paletteColor: Int override val paletteColor: Int
get() = Color.BLACK get() = Color.BLACK
@ -191,6 +211,11 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
override fun onPlayStateChanged() { override fun onPlayStateChanged() {
updatePlayPauseDrawableState() updatePlayPauseDrawableState()
if (MusicPlayerRemote.isPlaying) {
if (rotateAnimator?.isStarted == true) rotateAnimator?.resume() else rotateAnimator?.start()
} else {
rotateAnimator?.pause()
}
} }
override fun onPlayingMetaChanged() { override fun onPlayingMetaChanged() {
@ -202,6 +227,7 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
super.onServiceConnected() super.onServiceConnected()
updateSong() updateSong()
updatePlayPauseDrawableState() updatePlayPauseDrawableState()
setupRotateAnimation()
} }
private fun updateSong() { private fun updateSong() {
@ -215,6 +241,16 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
} else { } else {
binding.songInfo.hide() binding.songInfo.hide()
} }
GlideApp.with(this)
.load(RetroGlideExtension.getSongModel(MusicPlayerRemote.currentSong))
.simpleSongCoverOptions(MusicPlayerRemote.currentSong)
.thumbnail(lastRequest)
.error(GlideApp.with(this).load(R.drawable.default_audio_art).fitCenter())
.fitCenter().also {
lastRequest = it.clone()
it.crossfadeListener()
.into(binding.albumCover)
}
} }
private fun updatePlayPauseDrawableState() { private fun updatePlayPauseDrawableState() {
@ -225,8 +261,8 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
} }
override fun onAudioVolumeChanged(currentVolume: Int, maxVolume: Int) { override fun onAudioVolumeChanged(currentVolume: Int, maxVolume: Int) {
_binding?.volumeSeekBar?.max = maxVolume _binding?.volumeSeekBar?.max = maxVolume.toFloat()
_binding?.volumeSeekBar?.progress = currentVolume _binding?.volumeSeekBar?.progress = currentVolume.toFloat()
} }
override fun onDestroyView() { override fun onDestroyView() {
@ -237,15 +273,16 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
_binding = null _binding = null
} }
override fun onProgressChanged(seekArc: SeekArc?, progress: Int, fromUser: Boolean) {
override fun onProgressChanged(seekBar: CircularSeekBar?, progress: Float, fromUser: Boolean) {
val audioManager = audioManager val audioManager = audioManager
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, progress, 0) audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, progress.toInt(), 0)
} }
override fun onStartTrackingTouch(seekArc: SeekArc?) { override fun onStartTrackingTouch(seekBar: CircularSeekBar?) {
} }
override fun onStopTrackingTouch(seekArc: SeekArc?) { override fun onStopTrackingTouch(seekBar: CircularSeekBar?) {
} }
fun setUpProgressSlider() { fun setUpProgressSlider() {

View file

@ -28,7 +28,6 @@ import androidx.appcompat.widget.Toolbar
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
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.TintHelper import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
@ -39,6 +38,7 @@ import code.name.monkey.retromusic.databinding.FragmentClassicPlayerBinding
import code.name.monkey.retromusic.extensions.getSongInfo import code.name.monkey.retromusic.extensions.getSongInfo
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.extensions.surfaceColor
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.base.goToAlbum import code.name.monkey.retromusic.fragments.base.goToAlbum
@ -144,7 +144,7 @@ class ClassicPlayerFragment : AbsPlayerFragment(R.layout.fragment_classic_player
).build() ).build()
) )
shapeDrawable.fillColor = shapeDrawable.fillColor =
ColorStateList.valueOf(ATHUtil.resolveColor(requireContext(), R.attr.colorSurface)) ColorStateList.valueOf(surfaceColor())
binding.playerQueueSheet.background = shapeDrawable binding.playerQueueSheet.background = shapeDrawable
binding.playerQueueSheet.setOnTouchListener { _, _ -> binding.playerQueueSheet.setOnTouchListener { _, _ ->

View file

@ -19,10 +19,10 @@ import android.os.Bundle
import android.view.View import android.view.View
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.animation.doOnEnd import androidx.core.animation.doOnEnd
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.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentColorPlayerBinding import code.name.monkey.retromusic.databinding.FragmentColorPlayerBinding
import code.name.monkey.retromusic.extensions.colorControlNormal
import code.name.monkey.retromusic.extensions.drawAboveSystemBars import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment
@ -127,7 +127,7 @@ class ColorFragment : AbsPlayerFragment(R.layout.fragment_color_player) {
setOnMenuItemClickListener(this@ColorFragment) setOnMenuItemClickListener(this@ColorFragment)
ToolbarContentTintHelper.colorizeToolbar( ToolbarContentTintHelper.colorizeToolbar(
this, this,
ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal), colorControlNormal(),
requireActivity() requireActivity()
) )
} }

View file

@ -17,10 +17,10 @@ package code.name.monkey.retromusic.fragments.player.fit
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
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.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentFitBinding import code.name.monkey.retromusic.databinding.FragmentFitBinding
import code.name.monkey.retromusic.extensions.colorControlNormal
import code.name.monkey.retromusic.extensions.drawAboveSystemBars import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment
@ -56,7 +56,7 @@ class FitFragment : AbsPlayerFragment(R.layout.fragment_fit) {
} }
override fun toolbarIconColor(): Int { override fun toolbarIconColor(): Int {
return ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal) return colorControlNormal()
} }
override fun onColorChanged(color: MediaNotificationProcessor) { override fun onColorChanged(color: MediaNotificationProcessor) {
@ -65,7 +65,7 @@ class FitFragment : AbsPlayerFragment(R.layout.fragment_fit) {
libraryViewModel.updateColor(color.primaryTextColor) libraryViewModel.updateColor(color.primaryTextColor)
ToolbarContentTintHelper.colorizeToolbar( ToolbarContentTintHelper.colorizeToolbar(
binding.playerToolbar, binding.playerToolbar,
ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal), colorControlNormal(),
requireActivity() requireActivity()
) )
} }
@ -104,7 +104,7 @@ class FitFragment : AbsPlayerFragment(R.layout.fragment_fit) {
setOnMenuItemClickListener(this@FitFragment) setOnMenuItemClickListener(this@FitFragment)
ToolbarContentTintHelper.colorizeToolbar( ToolbarContentTintHelper.colorizeToolbar(
this, this,
ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal), colorControlNormal(),
requireActivity() requireActivity()
) )
} }

View file

@ -23,16 +23,12 @@ import android.view.animation.DecelerateInterpolator
import android.view.animation.LinearInterpolator import android.view.animation.LinearInterpolator
import android.widget.SeekBar import android.widget.SeekBar
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.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.appthemehelper.util.TintHelper import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentFitPlaybackControlsBinding import code.name.monkey.retromusic.databinding.FragmentFitPlaybackControlsBinding
import code.name.monkey.retromusic.extensions.getSongInfo import code.name.monkey.retromusic.extensions.*
import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.ripAlpha
import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.fragments.base.goToAlbum import code.name.monkey.retromusic.fragments.base.goToAlbum
import code.name.monkey.retromusic.fragments.base.goToArtist import code.name.monkey.retromusic.fragments.base.goToArtist
@ -132,8 +128,7 @@ class FitPlaybackControlsFragment :
} }
override fun setColor(color: MediaNotificationProcessor) { override fun setColor(color: MediaNotificationProcessor) {
val colorBg = ATHUtil.resolveColor(requireContext(), android.R.attr.colorBackground) if (ColorUtil.isColorLight(colorControlNormal())) {
if (ColorUtil.isColorLight(colorBg)) {
lastPlaybackControlsColor = lastPlaybackControlsColor =
MaterialValueHelper.getSecondaryTextColor(requireContext(), true) MaterialValueHelper.getSecondaryTextColor(requireContext(), true)
lastDisabledPlaybackControlsColor = lastDisabledPlaybackControlsColor =

View file

@ -20,12 +20,12 @@ import android.graphics.drawable.GradientDrawable
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.MaterialValueHelper
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.FragmentFlatPlayerBinding import code.name.monkey.retromusic.databinding.FragmentFlatPlayerBinding
import code.name.monkey.retromusic.extensions.colorControlNormal
import code.name.monkey.retromusic.extensions.drawAboveSystemBars import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment
@ -65,7 +65,7 @@ class FlatPlayerFragment : AbsPlayerFragment(R.layout.fragment_flat_player) {
binding.playerToolbar.setOnMenuItemClickListener(this) binding.playerToolbar.setOnMenuItemClickListener(this)
ToolbarContentTintHelper.colorizeToolbar( ToolbarContentTintHelper.colorizeToolbar(
binding.playerToolbar, binding.playerToolbar,
ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal), colorControlNormal(),
requireActivity() requireActivity()
) )
} }
@ -81,7 +81,7 @@ class FlatPlayerFragment : AbsPlayerFragment(R.layout.fragment_flat_player) {
GradientDrawable.Orientation.TOP_BOTTOM, GradientDrawable.Orientation.TOP_BOTTOM,
intArrayOf(animation.animatedValue as Int, android.R.color.transparent), 0 intArrayOf(animation.animatedValue as Int, android.R.color.transparent), 0
) )
binding.colorGradientBackground.background = drawable _binding?.colorGradientBackground?.background = drawable
} }
valueAnimator?.setDuration(ViewUtil.RETRO_MUSIC_ANIM_TIME.toLong())?.start() valueAnimator?.setDuration(ViewUtil.RETRO_MUSIC_ANIM_TIME.toLong())?.start()
} }
@ -91,7 +91,7 @@ class FlatPlayerFragment : AbsPlayerFragment(R.layout.fragment_flat_player) {
_binding = FragmentFlatPlayerBinding.bind(view) _binding = FragmentFlatPlayerBinding.bind(view)
setUpPlayerToolbar() setUpPlayerToolbar()
setUpSubFragments() setUpSubFragments()
binding.playbackControlsFragment.drawAboveSystemBars() binding.playerToolbar.drawAboveSystemBars()
} }
override fun onShow() { override fun onShow() {
@ -112,21 +112,16 @@ class FlatPlayerFragment : AbsPlayerFragment(R.layout.fragment_flat_player) {
return if (PreferenceUtil.isAdaptiveColor) return if (PreferenceUtil.isAdaptiveColor)
MaterialValueHelper.getPrimaryTextColor(requireContext(), isLight) MaterialValueHelper.getPrimaryTextColor(requireContext(), isLight)
else else
ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal) colorControlNormal()
} }
override fun onColorChanged(color: MediaNotificationProcessor) { override fun onColorChanged(color: MediaNotificationProcessor) {
lastColor = color.backgroundColor lastColor = color.backgroundColor
controlsFragment.setColor(color) controlsFragment.setColor(color)
libraryViewModel.updateColor(color.backgroundColor) libraryViewModel.updateColor(color.backgroundColor)
val isLight = ColorUtil.isColorLight(color.backgroundColor)
val iconColor = if (PreferenceUtil.isAdaptiveColor)
MaterialValueHelper.getPrimaryTextColor(requireContext(), isLight)
else
ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal)
ToolbarContentTintHelper.colorizeToolbar( ToolbarContentTintHelper.colorizeToolbar(
binding.playerToolbar, binding.playerToolbar,
iconColor, colorControlNormal(),
requireActivity() requireActivity()
) )
if (PreferenceUtil.isAdaptiveColor) { if (PreferenceUtil.isAdaptiveColor) {

View file

@ -26,14 +26,13 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.animation.DecelerateInterpolator import android.view.animation.DecelerateInterpolator
import android.view.animation.LinearInterpolator import android.view.animation.LinearInterpolator
import android.widget.PopupMenu
import android.widget.SeekBar import android.widget.SeekBar
import androidx.appcompat.widget.PopupMenu
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
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.databinding.FragmentFullPlayerControlsBinding import code.name.monkey.retromusic.databinding.FragmentFullPlayerControlsBinding
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.toSongEntity import code.name.monkey.retromusic.db.toSongEntity
import code.name.monkey.retromusic.extensions.applyColor import code.name.monkey.retromusic.extensions.applyColor
import code.name.monkey.retromusic.extensions.getSongInfo import code.name.monkey.retromusic.extensions.getSongInfo
@ -205,6 +204,7 @@ class FullPlaybackControlsFragment :
popupMenu.setOnMenuItemClickListener(this) popupMenu.setOnMenuItemClickListener(this)
popupMenu.inflate(R.menu.menu_player) popupMenu.inflate(R.menu.menu_player)
popupMenu.menu.findItem(R.id.action_toggle_favorite).isVisible = false popupMenu.menu.findItem(R.id.action_toggle_favorite).isVisible = false
popupMenu.menu.findItem(R.id.action_toggle_lyrics).isChecked = PreferenceUtil.showLyrics
popupMenu.show() popupMenu.show()
} }
} }
@ -317,11 +317,8 @@ class FullPlaybackControlsFragment :
fun updateIsFavorite(animate: Boolean = false) { fun updateIsFavorite(animate: Boolean = false) {
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
val playlist: PlaylistEntity = libraryViewModel.favoritePlaylist() val isFavorite: Boolean =
if (playlist != null) { libraryViewModel.isSongFavorite(MusicPlayerRemote.currentSong.id)
val song: SongEntity =
MusicPlayerRemote.currentSong.toSongEntity(playlist.playListId)
val isFavorite: Boolean = libraryViewModel.isFavoriteSong(song).isNotEmpty()
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
val icon = if (animate) { val icon = if (animate) {
if (isFavorite) R.drawable.avd_favorite else R.drawable.avd_unfavorite if (isFavorite) R.drawable.avd_favorite else R.drawable.avd_unfavorite
@ -344,7 +341,6 @@ class FullPlaybackControlsFragment :
} }
} }
} }
}
private fun toggleFavorite(song: Song) { private fun toggleFavorite(song: Song) {
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {

View file

@ -23,9 +23,9 @@ import android.graphics.drawable.AnimatedVectorDrawable
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.animation.LinearInterpolator import android.view.animation.LinearInterpolator
import android.widget.PopupMenu
import android.widget.SeekBar import android.widget.SeekBar
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
@ -39,9 +39,6 @@ import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.RetroBottomSheetBehavior import code.name.monkey.retromusic.RetroBottomSheetBehavior
import code.name.monkey.retromusic.adapter.song.PlayingQueueAdapter import code.name.monkey.retromusic.adapter.song.PlayingQueueAdapter
import code.name.monkey.retromusic.databinding.FragmentGradientPlayerBinding import code.name.monkey.retromusic.databinding.FragmentGradientPlayerBinding
import code.name.monkey.retromusic.db.PlaylistEntity
import code.name.monkey.retromusic.db.SongEntity
import code.name.monkey.retromusic.db.toSongEntity
import code.name.monkey.retromusic.extensions.* import code.name.monkey.retromusic.extensions.*
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
@ -81,7 +78,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
private var recyclerViewTouchActionGuardManager: RecyclerViewTouchActionGuardManager? = null private var recyclerViewTouchActionGuardManager: RecyclerViewTouchActionGuardManager? = null
private var playingQueueAdapter: PlayingQueueAdapter? = null private var playingQueueAdapter: PlayingQueueAdapter? = null
private lateinit var linearLayoutManager: LinearLayoutManager private lateinit var linearLayoutManager: LinearLayoutManager
private var bottomInsets = 0 private var navBarHeight = 0
private var _binding: FragmentGradientPlayerBinding? = null private var _binding: FragmentGradientPlayerBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
@ -92,8 +89,8 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
binding.playerQueueSheet.updatePadding( binding.playerQueueSheet.updatePadding(
top = (slideOffset * binding.statusBarLayout.statusBar.height).toInt() top = (slideOffset * binding.statusBarLayout.statusBar.height).toInt()
) )
binding.container.updatePadding( binding.recyclerView.updatePadding(
bottom = ((1 - slideOffset) * bottomInsets).toInt() top = ((1 - slideOffset) * navBarHeight).toInt()
) )
} }
@ -126,6 +123,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
popupMenu.setOnMenuItemClickListener(this) popupMenu.setOnMenuItemClickListener(this)
popupMenu.inflate(R.menu.menu_player) popupMenu.inflate(R.menu.menu_player)
popupMenu.menu.findItem(R.id.action_toggle_favorite).isVisible = false popupMenu.menu.findItem(R.id.action_toggle_favorite).isVisible = false
popupMenu.menu.findItem(R.id.action_toggle_lyrics).isChecked = PreferenceUtil.showLyrics
popupMenu.show() popupMenu.show()
} }
} }
@ -160,9 +158,9 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
} }
ViewCompat.setOnApplyWindowInsetsListener( ViewCompat.setOnApplyWindowInsetsListener(
(binding.container) (binding.container)
) { v: View, insets: WindowInsetsCompat -> ) { _: View, insets: WindowInsetsCompat ->
bottomInsets = insets.safeGetBottomInsets() navBarHeight = insets.safeGetBottomInsets()
v.updatePadding(bottom = bottomInsets) binding.recyclerView.updatePadding(top = navBarHeight)
insets insets
} }
binding.playbackControlsFragment.root.drawAboveSystemBars() binding.playbackControlsFragment.root.drawAboveSystemBars()
@ -281,11 +279,8 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
private fun updateIsFavoriteIcon(animate: Boolean = false) { private fun updateIsFavoriteIcon(animate: Boolean = false) {
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
val playlist: PlaylistEntity = libraryViewModel.favoritePlaylist() val isFavorite: Boolean =
if (playlist != null) { libraryViewModel.isSongFavorite(MusicPlayerRemote.currentSong.id)
val song: SongEntity =
MusicPlayerRemote.currentSong.toSongEntity(playlist.playListId)
val isFavorite: Boolean = libraryViewModel.isFavoriteSong(song).isNotEmpty()
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
val icon = if (animate) { val icon = if (animate) {
if (isFavorite) R.drawable.avd_favorite else R.drawable.avd_unfavorite if (isFavorite) R.drawable.avd_favorite else R.drawable.avd_unfavorite
@ -303,7 +298,6 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
} }
} }
} }
}
private fun hideVolumeIfAvailable() { private fun hideVolumeIfAvailable() {
if (PreferenceUtil.isVolumeVisibilityMode) { if (PreferenceUtil.isVolumeVisibilityMode) {
@ -479,7 +473,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
oldBottom: Int oldBottom: Int
) { ) {
val panel = getQueuePanel() val panel = getQueuePanel()
panel.peekHeight = binding.container.height panel.peekHeight = binding.container.height + navBarHeight
} }
private fun setupRecyclerView() { private fun setupRecyclerView() {

View file

@ -18,10 +18,10 @@ import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
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.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentHomePlayerBinding import code.name.monkey.retromusic.databinding.FragmentHomePlayerBinding
import code.name.monkey.retromusic.extensions.colorControlNormal
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
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
@ -128,7 +128,7 @@ class HomePlayerFragment : AbsPlayerFragment(R.layout.fragment_home_player),
ToolbarContentTintHelper.colorizeToolbar( ToolbarContentTintHelper.colorizeToolbar(
binding.playerToolbar, binding.playerToolbar,
ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal), colorControlNormal(),
requireActivity() requireActivity()
) )
} }

View file

@ -17,10 +17,10 @@ package code.name.monkey.retromusic.fragments.player.material
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
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.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentMaterialBinding import code.name.monkey.retromusic.databinding.FragmentMaterialBinding
import code.name.monkey.retromusic.extensions.colorControlNormal
import code.name.monkey.retromusic.extensions.drawAboveSystemBars import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment
@ -62,9 +62,7 @@ class MaterialFragment : AbsPlayerFragment(R.layout.fragment_material) {
return false return false
} }
override fun toolbarIconColor(): Int { override fun toolbarIconColor() = colorControlNormal()
return ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal)
}
override fun onColorChanged(color: MediaNotificationProcessor) { override fun onColorChanged(color: MediaNotificationProcessor) {
playbackControlsFragment.setColor(color) playbackControlsFragment.setColor(color)
@ -73,7 +71,7 @@ class MaterialFragment : AbsPlayerFragment(R.layout.fragment_material) {
ToolbarContentTintHelper.colorizeToolbar( ToolbarContentTintHelper.colorizeToolbar(
binding.playerToolbar, binding.playerToolbar,
ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal), colorControlNormal(),
requireActivity() requireActivity()
) )
} }
@ -112,7 +110,7 @@ class MaterialFragment : AbsPlayerFragment(R.layout.fragment_material) {
setOnMenuItemClickListener(this@MaterialFragment) setOnMenuItemClickListener(this@MaterialFragment)
ToolbarContentTintHelper.colorizeToolbar( ToolbarContentTintHelper.colorizeToolbar(
this, this,
ATHUtil.resolveColor(context, R.attr.colorControlNormal), colorControlNormal(),
requireActivity() requireActivity()
) )
} }

View file

@ -16,15 +16,21 @@ package code.name.monkey.retromusic.fragments.player.normal
import android.animation.ArgbEvaluator import android.animation.ArgbEvaluator
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.content.SharedPreferences
import android.graphics.drawable.GradientDrawable import android.graphics.drawable.GradientDrawable
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import code.name.monkey.appthemehelper.util.ATHUtil import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.preference.PreferenceManager
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.SNOWFALL
import code.name.monkey.retromusic.databinding.FragmentPlayerBinding import code.name.monkey.retromusic.databinding.FragmentPlayerBinding
import code.name.monkey.retromusic.extensions.colorControlNormal
import code.name.monkey.retromusic.extensions.drawAboveSystemBars import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
@ -34,7 +40,8 @@ import code.name.monkey.retromusic.util.ViewUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import code.name.monkey.retromusic.views.DrawableGradient import code.name.monkey.retromusic.views.DrawableGradient
class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player) { class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player),
SharedPreferences.OnSharedPreferenceChangeListener {
private var lastColor: Int = 0 private var lastColor: Int = 0
override val paletteColor: Int override val paletteColor: Int
@ -54,7 +61,7 @@ class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player) {
valueAnimator = ValueAnimator.ofObject( valueAnimator = ValueAnimator.ofObject(
ArgbEvaluator(), ArgbEvaluator(),
ATHUtil.resolveColor(requireContext(), R.attr.colorSurface), surfaceColor(),
i i
) )
valueAnimator?.addUpdateListener { animation -> valueAnimator?.addUpdateListener { animation ->
@ -63,7 +70,7 @@ class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player) {
GradientDrawable.Orientation.TOP_BOTTOM, GradientDrawable.Orientation.TOP_BOTTOM,
intArrayOf( intArrayOf(
animation.animatedValue as Int, animation.animatedValue as Int,
ATHUtil.resolveColor(requireContext(), R.attr.colorSurface) surfaceColor()
), 0 ), 0
) )
binding.colorGradientBackground.background = drawable binding.colorGradientBackground.background = drawable
@ -85,9 +92,7 @@ class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player) {
return false return false
} }
override fun toolbarIconColor(): Int { override fun toolbarIconColor() = colorControlNormal()
return ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal)
}
override fun onColorChanged(color: MediaNotificationProcessor) { override fun onColorChanged(color: MediaNotificationProcessor) {
controlsFragment.setColor(color) controlsFragment.setColor(color)
@ -96,7 +101,7 @@ class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player) {
ToolbarContentTintHelper.colorizeToolbar( ToolbarContentTintHelper.colorizeToolbar(
binding.playerToolbar, binding.playerToolbar,
ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal), colorControlNormal(),
requireActivity() requireActivity()
) )
@ -121,11 +126,23 @@ class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player) {
_binding = FragmentPlayerBinding.bind(view) _binding = FragmentPlayerBinding.bind(view)
setUpSubFragments() setUpSubFragments()
setUpPlayerToolbar() setUpPlayerToolbar()
if (PreferenceUtil.isSnowFalling) {
binding.snowfallView.isVisible = true
binding.snowfallView.restartFalling()
} else {
binding.snowfallView.isVisible = false
binding.snowfallView.stopFalling()
}
PreferenceManager.getDefaultSharedPreferences(requireContext())
.registerOnSharedPreferenceChangeListener(this)
playerToolbar().drawAboveSystemBars() playerToolbar().drawAboveSystemBars()
} }
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
PreferenceManager.getDefaultSharedPreferences(requireContext())
.unregisterOnSharedPreferenceChangeListener(this)
_binding = null _binding = null
} }
@ -145,11 +162,21 @@ class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player) {
ToolbarContentTintHelper.colorizeToolbar( ToolbarContentTintHelper.colorizeToolbar(
binding.playerToolbar, binding.playerToolbar,
ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal), colorControlNormal(),
requireActivity() requireActivity()
) )
} }
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (key == SNOWFALL && PreferenceUtil.isSnowFalling) {
binding.snowfallView.isVisible = true
binding.snowfallView.restartFalling()
} else {
binding.snowfallView.isVisible = false
binding.snowfallView.stopFalling()
}
}
override fun onServiceConnected() { override fun onServiceConnected() {
updateIsFavorite() updateIsFavorite()
} }

View file

@ -17,14 +17,10 @@ package code.name.monkey.retromusic.fragments.player.peak
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
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.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentPeakPlayerBinding import code.name.monkey.retromusic.databinding.FragmentPeakPlayerBinding
import code.name.monkey.retromusic.extensions.drawAboveSystemBars import code.name.monkey.retromusic.extensions.*
import code.name.monkey.retromusic.extensions.getSongInfo
import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.base.goToAlbum import code.name.monkey.retromusic.fragments.base.goToAlbum
import code.name.monkey.retromusic.fragments.base.goToArtist import code.name.monkey.retromusic.fragments.base.goToArtist
@ -57,7 +53,7 @@ class PeakPlayerFragment : AbsPlayerFragment(R.layout.fragment_peak_player) {
binding.text.setOnClickListener { binding.text.setOnClickListener {
goToArtist(requireActivity()) goToArtist(requireActivity())
} }
binding.root.drawAboveSystemBars() binding.root.drawAboveSystemBarsWithPadding()
} }
private fun setUpSubFragments() { private fun setUpSubFragments() {
@ -76,7 +72,7 @@ class PeakPlayerFragment : AbsPlayerFragment(R.layout.fragment_peak_player) {
setOnMenuItemClickListener(this@PeakPlayerFragment) setOnMenuItemClickListener(this@PeakPlayerFragment)
ToolbarContentTintHelper.colorizeToolbar( ToolbarContentTintHelper.colorizeToolbar(
this, this,
ATHUtil.resolveColor(context, R.attr.colorControlNormal), colorControlNormal(),
requireActivity() requireActivity()
) )
} }
@ -96,9 +92,7 @@ class PeakPlayerFragment : AbsPlayerFragment(R.layout.fragment_peak_player) {
return false return false
} }
override fun toolbarIconColor(): Int { override fun toolbarIconColor() = colorControlNormal()
return ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal)
}
override val paletteColor: Int override val paletteColor: Int
get() = lastColor get() = lastColor

View file

@ -17,10 +17,10 @@ package code.name.monkey.retromusic.fragments.player.plain
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
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.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentPlainPlayerBinding import code.name.monkey.retromusic.databinding.FragmentPlainPlayerBinding
import code.name.monkey.retromusic.extensions.colorControlNormal
import code.name.monkey.retromusic.extensions.drawAboveSystemBars import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.base.goToAlbum import code.name.monkey.retromusic.fragments.base.goToAlbum
@ -66,7 +66,7 @@ class PlainPlayerFragment : AbsPlayerFragment(R.layout.fragment_plain_player) {
setOnMenuItemClickListener(this@PlainPlayerFragment) setOnMenuItemClickListener(this@PlainPlayerFragment)
ToolbarContentTintHelper.colorizeToolbar( ToolbarContentTintHelper.colorizeToolbar(
this, this,
ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal), colorControlNormal(),
requireActivity() requireActivity()
) )
} }
@ -109,9 +109,7 @@ class PlainPlayerFragment : AbsPlayerFragment(R.layout.fragment_plain_player) {
return false return false
} }
override fun toolbarIconColor(): Int { override fun toolbarIconColor() = colorControlNormal()
return ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal)
}
override fun onColorChanged(color: MediaNotificationProcessor) { override fun onColorChanged(color: MediaNotificationProcessor) {
plainPlaybackControlsFragment.setColor(color) plainPlaybackControlsFragment.setColor(color)
@ -119,7 +117,7 @@ class PlainPlayerFragment : AbsPlayerFragment(R.layout.fragment_plain_player) {
libraryViewModel.updateColor(color.primaryTextColor) libraryViewModel.updateColor(color.primaryTextColor)
ToolbarContentTintHelper.colorizeToolbar( ToolbarContentTintHelper.colorizeToolbar(
binding.playerToolbar, binding.playerToolbar,
ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal), colorControlNormal(),
requireActivity() requireActivity()
) )
} }

View file

@ -17,10 +17,10 @@ package code.name.monkey.retromusic.fragments.player.simple
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
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.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentSimplePlayerBinding import code.name.monkey.retromusic.databinding.FragmentSimplePlayerBinding
import code.name.monkey.retromusic.extensions.colorControlNormal
import code.name.monkey.retromusic.extensions.drawAboveSystemBars import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment
@ -75,9 +75,7 @@ class SimplePlayerFragment : AbsPlayerFragment(R.layout.fragment_simple_player)
return false return false
} }
override fun toolbarIconColor(): Int { override fun toolbarIconColor() = colorControlNormal()
return ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal)
}
override fun onColorChanged(color: MediaNotificationProcessor) { override fun onColorChanged(color: MediaNotificationProcessor) {
lastColor = color.backgroundColor lastColor = color.backgroundColor
@ -85,7 +83,7 @@ class SimplePlayerFragment : AbsPlayerFragment(R.layout.fragment_simple_player)
controlsFragment.setColor(color) controlsFragment.setColor(color)
ToolbarContentTintHelper.colorizeToolbar( ToolbarContentTintHelper.colorizeToolbar(
binding.playerToolbar, binding.playerToolbar,
ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal), colorControlNormal(),
requireActivity() requireActivity()
) )
} }
@ -107,7 +105,7 @@ class SimplePlayerFragment : AbsPlayerFragment(R.layout.fragment_simple_player)
binding.playerToolbar.setOnMenuItemClickListener(this) binding.playerToolbar.setOnMenuItemClickListener(this)
ToolbarContentTintHelper.colorizeToolbar( ToolbarContentTintHelper.colorizeToolbar(
binding.playerToolbar, binding.playerToolbar,
ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal), colorControlNormal(),
requireActivity() requireActivity()
) )
} }

View file

@ -281,12 +281,13 @@ class TinyPlayerFragment : AbsPlayerFragment(R.layout.fragment_tiny_player),
return gestureDetector.onTouchEvent(event) return gestureDetector.onTouchEvent(event)
} }
@Suppress("Deprecation")
private fun vibrate() { private fun vibrate() {
val v = requireContext().getSystemService(Context.VIBRATOR_SERVICE) as Vibrator? val v = requireContext().getSystemService(Context.VIBRATOR_SERVICE) as Vibrator?
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
v!!.vibrate(VibrationEffect.createOneShot(10, VibrationEffect.DEFAULT_AMPLITUDE)) v?.vibrate(VibrationEffect.createOneShot(10, VibrationEffect.DEFAULT_AMPLITUDE))
} else { } else {
v!!.vibrate(10) v?.vibrate(10)
} }
} }
} }

View file

@ -42,7 +42,7 @@ class PlaylistsFragment :
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
libraryViewModel.getPlaylists().observe(viewLifecycleOwner, { libraryViewModel.getPlaylists().observe(viewLifecycleOwner, {
if (it.isNotEmpty()) if (it.isNotEmpty())
adapter?.swapDataSet(it.filter { playlistWithSongs-> playlistWithSongs.songs.isNotEmpty() }) adapter?.swapDataSet(it)
else else
adapter?.swapDataSet(listOf()) adapter?.swapDataSet(listOf())
}) })

View file

@ -43,6 +43,7 @@ import com.google.android.material.chip.ChipGroup
import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.transition.MaterialSharedAxis import com.google.android.material.transition.MaterialSharedAxis
import net.yslibrary.android.keyboardvisibilityevent.KeyboardVisibilityEvent
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@ -70,7 +71,10 @@ class SearchFragment : AbsMainActivityFragment(R.layout.fragment_search), TextWa
setupRecyclerView() setupRecyclerView()
binding.voiceSearch.setOnClickListener { startMicSearch() } binding.voiceSearch.setOnClickListener { startMicSearch() }
binding.clearText.setOnClickListener { binding.searchView.clearText() } binding.clearText.setOnClickListener {
binding.searchView.clearText()
searchAdapter.swapDataSet(listOf())
}
binding.searchView.apply { binding.searchView.apply {
addTextChangedListener(this@SearchFragment) addTextChangedListener(this@SearchFragment)
focusAndShowKeyboard() focusAndShowKeyboard()
@ -97,6 +101,13 @@ class SearchFragment : AbsMainActivityFragment(R.layout.fragment_search), TextWa
bottomMargin = it bottomMargin = it
} }
}) })
KeyboardVisibilityEvent.setEventListener(requireActivity(), viewLifecycleOwner) {
if (it) {
binding.keyboardPopup.isGone = true
} else {
binding.keyboardPopup.show()
}
}
binding.appBarLayout.statusBarForeground = binding.appBarLayout.statusBarForeground =
MaterialShapeDrawable.createWithElevationOverlay(requireContext()) MaterialShapeDrawable.createWithElevationOverlay(requireContext())
} }

View file

@ -16,16 +16,19 @@ package code.name.monkey.retromusic.fragments.settings
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import code.name.monkey.appthemehelper.common.prefs.supportv7.ATEPreferenceFragmentCompat import code.name.monkey.appthemehelper.common.prefs.supportv7.ATEPreferenceFragmentCompat
import code.name.monkey.retromusic.activities.OnThemeChangedListener import code.name.monkey.retromusic.activities.OnThemeChangedListener
import code.name.monkey.retromusic.extensions.rootView
import code.name.monkey.retromusic.extensions.safeGetBottomInsets import code.name.monkey.retromusic.extensions.safeGetBottomInsets
import code.name.monkey.retromusic.preferences.* import code.name.monkey.retromusic.preferences.*
import code.name.monkey.retromusic.util.NavigationUtil import code.name.monkey.retromusic.util.NavigationUtil
@ -67,14 +70,23 @@ abstract class AbsSettingsFragment : ATEPreferenceFragmentCompat() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setDivider(ColorDrawable(Color.TRANSPARENT)) setDivider(ColorDrawable(Color.TRANSPARENT))
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
listView.overScrollMode = View.OVER_SCROLL_NEVER
}
// CollapsingToolbarLayout consumes insets and insets are not passed to child views // CollapsingToolbarLayout consumes insets and insets are not passed to child views
// So we get insets from root view // So we get insets from decor view
// https://github.com/material-components/material-components-android/issues/1310 // https://github.com/material-components/material-components-android/issues/1310
ViewCompat.setOnApplyWindowInsetsListener( ViewCompat.setOnApplyWindowInsetsListener(
view requireActivity().rootView
) { _, insets -> ) { _, windowInsets ->
listView.updatePadding(bottom = insets.safeGetBottomInsets()) val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
insets listView.updatePadding(
left = insets.left,
bottom = insets.bottom,
right = insets.right,
)
windowInsets
} }
invalidateSettings() invalidateSettings()
} }

View file

@ -19,6 +19,8 @@ import android.os.Bundle
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 androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
@ -26,11 +28,10 @@ import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.retromusic.App import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentMainSettingsBinding import code.name.monkey.retromusic.databinding.FragmentMainSettingsBinding
import code.name.monkey.retromusic.extensions.addBottomInsets
import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.rootView
import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.util.NavigationUtil import code.name.monkey.retromusic.util.NavigationUtil
import code.name.monkey.retromusic.util.RetroUtil
class MainSettingsFragment : Fragment(), View.OnClickListener { class MainSettingsFragment : Fragment(), View.OnClickListener {
@ -87,8 +88,17 @@ class MainSettingsFragment : Fragment(), View.OnClickListener {
binding.buyPremium.setTextColor(it) binding.buyPremium.setTextColor(it)
binding.diamondIcon.imageTintList = ColorStateList.valueOf(it) binding.diamondIcon.imageTintList = ColorStateList.valueOf(it)
} }
if (!RetroUtil.isLandscape()) {
binding.container.updatePadding(bottom = RetroUtil.getNavigationBarHeight()) ViewCompat.setOnApplyWindowInsetsListener(
requireActivity().rootView
) { _, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
_binding?.container?.updatePadding(
left = insets.left,
bottom = insets.bottom,
right = insets.right,
)
windowInsets
} }
} }

View file

@ -23,9 +23,15 @@ import com.bumptech.glide.RequestBuilder
import com.bumptech.glide.annotation.GlideExtension import com.bumptech.glide.annotation.GlideExtension
import com.bumptech.glide.annotation.GlideOption import com.bumptech.glide.annotation.GlideOption
import com.bumptech.glide.annotation.GlideType import com.bumptech.glide.annotation.GlideType
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.Key import com.bumptech.glide.load.Key
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.BaseRequestOptions import com.bumptech.glide.request.BaseRequestOptions
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.bumptech.glide.request.transition.DrawableCrossFadeFactory
import com.bumptech.glide.request.transition.Transition
import com.bumptech.glide.signature.MediaStoreSignature import com.bumptech.glide.signature.MediaStoreSignature
import java.io.File import java.io.File
@ -116,6 +122,16 @@ object RetroGlideExtension {
.signature(createSignature(song)) .signature(createSignature(song))
} }
@JvmStatic
@GlideOption
fun simpleSongCoverOptions(
baseRequestOptions: BaseRequestOptions<*>,
song: Song
): BaseRequestOptions<*> {
return baseRequestOptions.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
.signature(createSignature(song))
}
@JvmStatic @JvmStatic
@GlideOption @GlideOption
fun albumCoverOptions( fun albumCoverOptions(
@ -195,3 +211,32 @@ object RetroGlideExtension {
return GenericTransitionOptions<TranscodeType>().transition(DEFAULT_ANIMATION) return GenericTransitionOptions<TranscodeType>().transition(DEFAULT_ANIMATION)
} }
} }
// https://github.com/bumptech/glide/issues/527#issuecomment-148840717
fun GlideRequest<Drawable>.crossfadeListener(): GlideRequest<Drawable> {
return listener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
return if (isFirstResource) {
false // thumbnail was not shown, do as usual
} else DrawableCrossFadeFactory.Builder()
.setCrossFadeEnabled(true).build()
.build(dataSource, isFirstResource)
.transition(resource, target as Transition.ViewAdapter)
}
})
}

View file

@ -16,9 +16,8 @@ package code.name.monkey.retromusic.glide
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.widget.ImageView import android.widget.ImageView
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.retromusic.App import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.extensions.colorControlNormal
import code.name.monkey.retromusic.glide.palette.BitmapPaletteTarget import code.name.monkey.retromusic.glide.palette.BitmapPaletteTarget
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
@ -27,7 +26,7 @@ import com.bumptech.glide.request.transition.Transition
abstract class RetroMusicColoredTarget(view: ImageView) : BitmapPaletteTarget(view) { abstract class RetroMusicColoredTarget(view: ImageView) : BitmapPaletteTarget(view) {
protected val defaultFooterColor: Int protected val defaultFooterColor: Int
get() = ATHUtil.resolveColor(getView().context, R.attr.colorControlNormal) get() = getView().context.colorControlNormal()
abstract fun onColorReady(colors: MediaNotificationProcessor) abstract fun onColorReady(colors: MediaNotificationProcessor)

View file

@ -14,6 +14,7 @@
package code.name.monkey.retromusic.glide.audiocover; package code.name.monkey.retromusic.glide.audiocover;
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.audio.mp3.MP3File; import org.jaudiotagger.audio.mp3.MP3File;
@ -45,7 +46,7 @@ public class AudioFileCoverUtils {
} }
} }
// If there are any exceptions, we ignore them and continue to the other fallback method // If there are any exceptions, we ignore them and continue to the other fallback method
} catch (ReadOnlyFileException | InvalidAudioFrameException | TagException | IOException ignored) { } catch (ReadOnlyFileException | InvalidAudioFrameException | TagException | IOException | CannotReadException ignored) {
} }
// Method 2: look for album art in external files // Method 2: look for album art in external files

View file

@ -2,9 +2,13 @@ package code.name.monkey.retromusic.helper
import android.content.Context import android.content.Context
import android.os.Environment import android.os.Environment
import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import code.name.monkey.retromusic.App import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.BuildConfig import code.name.monkey.retromusic.BuildConfig
import code.name.monkey.retromusic.helper.BackupContent.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.* import java.io.*
@ -24,10 +28,11 @@ object BackupHelper {
zipItems.addAll(getSettingsZipItems(context)) zipItems.addAll(getSettingsZipItems(context))
getUserImageZipItems(context)?.let { zipItems.addAll(it) } getUserImageZipItems(context)?.let { zipItems.addAll(it) }
zipItems.addAll(getCustomArtistZipItems(context)) zipItems.addAll(getCustomArtistZipItems(context))
zipItems.addAll(getQueueZipItems(context))
zipAll(zipItems, backupFile) zipAll(zipItems, backupFile)
} }
private suspend fun zipAll(zipItems: List<ZipItem>, backupFile: File) { private suspend fun zipAll(zipItems: List<ZipItem>, backupFile: File) =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
kotlin.runCatching { kotlin.runCatching {
ZipOutputStream(BufferedOutputStream(FileOutputStream(backupFile))).use { out -> ZipOutputStream(BufferedOutputStream(FileOutputStream(backupFile))).use { out ->
@ -42,27 +47,42 @@ object BackupHelper {
} }
} }
}.onFailure { }.onFailure {
it.printStackTrace()
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
Toast.makeText(App.getContext(), "Couldn't create backup", Toast.LENGTH_SHORT) Toast.makeText(App.getContext(), "Couldn't create backup", Toast.LENGTH_SHORT)
.show() .show()
} }
} throw Exception(it)
}.onSuccess {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
Toast.makeText(App.getContext(), "Backup created successfully", Toast.LENGTH_SHORT) Toast.makeText(
App.getContext(),
"Backup created successfully",
Toast.LENGTH_SHORT
)
.show() .show()
} }
} }
} }
private fun getDatabaseZipItems(context: Context): List<ZipItem> { private fun getDatabaseZipItems(context: Context): List<ZipItem> {
return context.databaseList().filter { return context.databaseList().filter {
it.endsWith(".db") it.endsWith(".db") && it != queueDatabase
}.map { }.map {
ZipItem(context.getDatabasePath(it).absolutePath, "$DATABASES_PATH${File.separator}$it") ZipItem(context.getDatabasePath(it).absolutePath, "$DATABASES_PATH${File.separator}$it")
} }
} }
private fun getQueueZipItems(context: Context): List<ZipItem> {
Log.d("RetroMusic", context.getDatabasePath(queueDatabase).absolutePath)
return listOf(
ZipItem(
context.getDatabasePath(queueDatabase).absolutePath,
"$QUEUE_PATH${File.separator}$queueDatabase"
)
)
}
private fun getSettingsZipItems(context: Context): List<ZipItem> { private fun getSettingsZipItems(context: Context): List<ZipItem> {
val sharedPrefPath = context.filesDir.parentFile?.absolutePath + "/shared_prefs/" val sharedPrefPath = context.filesDir.parentFile?.absolutePath + "/shared_prefs/"
return listOf( return listOf(
@ -94,33 +114,47 @@ object BackupHelper {
) )
}?.toList() ?: listOf() }?.toList() ?: listOf()
) )
File(sharedPrefPath + File.separator + "custom_artist_image.xml").let {
if (it.exists()) {
zipItemList.add( zipItemList.add(
ZipItem( ZipItem(
sharedPrefPath + File.separator + "custom_artist_image.xml", it.absolutePath,
"$CUSTOM_ARTISTS_PATH${File.separator}prefs${File.separator}custom_artist_image.xml" "$CUSTOM_ARTISTS_PATH${File.separator}prefs${File.separator}custom_artist_image.xml"
) )
) )
}
}
return zipItemList return zipItemList
} }
suspend fun restoreBackup(context: Context, inputStream: InputStream?) { suspend fun restoreBackup(
context: Context,
inputStream: InputStream?,
contents: List<BackupContent>
) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
ZipInputStream(inputStream).use { ZipInputStream(inputStream).use {
var entry = it.nextEntry var entry = it.nextEntry
while (entry != null) { while (entry != null) {
if (entry.isDatabaseEntry()) restoreDatabase(context, it, entry) if (entry.isDatabaseEntry() && contents.contains(PLAYLISTS)) {
if (entry.isPreferenceEntry()) restorePreferences(context, it, entry) restoreDatabase(context, it, entry)
if (entry.isImageEntry()) restoreImages(context, it, entry) } else if (entry.isPreferenceEntry() && contents.contains(SETTINGS)) {
if (entry.isCustomArtistImageEntry()) restoreCustomArtistImages( restorePreferences(context, it, entry)
context, } else if (entry.isImageEntry() && contents.contains(USER_IMAGES)) {
it, restoreImages(context, it, entry)
entry
) } else if (entry.isCustomArtistImageEntry() && contents.contains(
if (entry.isCustomArtistPrefEntry()) restoreCustomArtistPrefs( CUSTOM_ARTIST_IMAGES
context,
it,
entry
) )
) {
restoreCustomArtistImages(context, it, entry)
restoreCustomArtistPrefs(context, it, entry)
} else if (entry.isQueueEntry() && contents.contains(QUEUE)) {
restoreQueueDatabase(context, it, entry)
}
entry = it.nextEntry entry = it.nextEntry
} }
} }
@ -170,6 +204,21 @@ object BackupHelper {
} }
} }
private fun restoreQueueDatabase(context: Context, zipIn: ZipInputStream, zipEntry: ZipEntry) {
PreferenceManager.getDefaultSharedPreferences(context).edit(commit = true) {
putInt("POSITION", 0)
}
val filePath =
context.filesDir.parent!! + File.separator + DATABASES_PATH + File.separator + zipEntry.getFileName()
BufferedOutputStream(FileOutputStream(filePath)).use { bos ->
val bytesIn = ByteArray(DEFAULT_BUFFER_SIZE)
var read: Int
while (zipIn.read(bytesIn).also { read = it } != -1) {
bos.write(bytesIn, 0, read)
}
}
}
private fun restoreCustomArtistImages( private fun restoreCustomArtistImages(
context: Context, context: Context,
zipIn: ZipInputStream, zipIn: ZipInputStream,
@ -218,10 +267,12 @@ object BackupHelper {
const val BACKUP_EXTENSION = "rmbak" const val BACKUP_EXTENSION = "rmbak"
const val APPEND_EXTENSION = ".$BACKUP_EXTENSION" const val APPEND_EXTENSION = ".$BACKUP_EXTENSION"
private const val DATABASES_PATH = "databases" private const val DATABASES_PATH = "databases"
private const val QUEUE_PATH = "queue"
private const val SETTINGS_PATH = "prefs" private const val SETTINGS_PATH = "prefs"
private const val IMAGES_PATH = "userImages" private const val IMAGES_PATH = "userImages"
private const val CUSTOM_ARTISTS_PATH = "artistImages" private const val CUSTOM_ARTISTS_PATH = "artistImages"
private const val THEME_PREFS_KEY_DEFAULT = "[[kabouzeid_app-theme-helper]]" private const val THEME_PREFS_KEY_DEFAULT = "[[kabouzeid_app-theme-helper]]"
private const val queueDatabase = "music_playback_state.db"
private fun ZipEntry.isDatabaseEntry(): Boolean { private fun ZipEntry.isDatabaseEntry(): Boolean {
return name.startsWith(DATABASES_PATH) return name.startsWith(DATABASES_PATH)
@ -243,6 +294,10 @@ object BackupHelper {
return name.startsWith(CUSTOM_ARTISTS_PATH) && name.contains("prefs") return name.startsWith(CUSTOM_ARTISTS_PATH) && name.contains("prefs")
} }
private fun ZipEntry.isQueueEntry(): Boolean {
return name.startsWith(QUEUE_PATH)
}
private fun ZipEntry.getFileName(): String { private fun ZipEntry.getFileName(): String {
return name.substring(name.lastIndexOf(File.separator)) return name.substring(name.lastIndexOf(File.separator))
} }
@ -262,3 +317,11 @@ fun CharSequence.sanitize(): String {
.replace("\\", "_") .replace("\\", "_")
.replace("&", "_") .replace("&", "_")
} }
enum class BackupContent {
SETTINGS,
USER_IMAGES,
CUSTOM_ARTIST_IMAGES,
PLAYLISTS,
QUEUE
}

View file

@ -31,7 +31,7 @@ object M3UWriter : M3UConstants {
val file = File(dir, playlist.name + "." + M3UConstants.EXTENSION) val file = File(dir, playlist.name + "." + M3UConstants.EXTENSION)
val songs = playlist.getSongs() val songs = playlist.getSongs()
if (songs.isNotEmpty()) { if (songs.isNotEmpty()) {
val bw = BufferedWriter(FileWriter(file)) BufferedWriter(FileWriter(file)).use { bw ->
bw.write(M3UConstants.HEADER) bw.write(M3UConstants.HEADER)
for (song in songs) { for (song in songs) {
bw.newLine() bw.newLine()
@ -39,7 +39,7 @@ object M3UWriter : M3UConstants {
bw.newLine() bw.newLine()
bw.write(song.data) bw.write(song.data)
} }
bw.close() }
} }
return file return file
} }
@ -54,15 +54,15 @@ object M3UWriter : M3UConstants {
it.songPrimaryKey it.songPrimaryKey
}.toSongs() }.toSongs()
if (songs.isNotEmpty()) { if (songs.isNotEmpty()) {
val bufferedWriter = BufferedWriter(FileWriter(file)) BufferedWriter(FileWriter(file)).use { bw->
bufferedWriter.write(M3UConstants.HEADER) bw.write(M3UConstants.HEADER)
songs.forEach { songs.forEach {
bufferedWriter.newLine() bw.newLine()
bufferedWriter.write(M3UConstants.ENTRY + it.duration + M3UConstants.DURATION_SEPARATOR + it.artistName + " - " + it.title) bw.write(M3UConstants.ENTRY + it.duration + M3UConstants.DURATION_SEPARATOR + it.artistName + " - " + it.title)
bufferedWriter.newLine() bw.newLine()
bufferedWriter.write(it.data) bw.write(it.data)
}
} }
bufferedWriter.close()
} }
return file return file
} }
@ -72,15 +72,15 @@ object M3UWriter : M3UConstants {
it.songPrimaryKey it.songPrimaryKey
}.toSongs() }.toSongs()
if (songs.isNotEmpty()) { if (songs.isNotEmpty()) {
val bufferedWriter = outputStream.bufferedWriter() outputStream.bufferedWriter().use{ bw->
bufferedWriter.write(M3UConstants.HEADER) bw.write(M3UConstants.HEADER)
songs.forEach { songs.forEach {
bufferedWriter.newLine() bw.newLine()
bufferedWriter.write(M3UConstants.ENTRY + it.duration + M3UConstants.DURATION_SEPARATOR + it.artistName + " - " + it.title) bw.write(M3UConstants.ENTRY + it.duration + M3UConstants.DURATION_SEPARATOR + it.artistName + " - " + it.title)
bufferedWriter.newLine() bw.newLine()
bufferedWriter.write(it.data) bw.write(it.data)
}
} }
bufferedWriter.close()
} }
outputStream.flush() outputStream.flush()
outputStream.close() outputStream.close()

View file

@ -17,7 +17,7 @@ package code.name.monkey.retromusic.helper.menu
import android.content.Intent import android.content.Intent
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.navigation.findNavController import androidx.navigation.findNavController

View file

@ -0,0 +1,5 @@
package code.name.monkey.retromusic.interfaces
interface IScrollHelper {
fun scrollToTop()
}

View file

@ -0,0 +1,719 @@
/*
* Copyright (C) 2017 wangchenyan
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package code.name.monkey.retromusic.lyrics
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.drawable.Drawable
import android.os.AsyncTask
import android.os.Looper
import android.text.Layout
import android.text.StaticLayout
import android.text.TextPaint
import android.text.TextUtils
import android.text.format.DateUtils
import android.util.AttributeSet
import android.view.GestureDetector
import android.view.GestureDetector.SimpleOnGestureListener
import android.view.MotionEvent
import android.view.View
import android.view.animation.LinearInterpolator
import android.widget.Scroller
import androidx.core.content.ContextCompat
import code.name.monkey.retromusic.R
import java.io.File
import java.util.*
import kotlin.math.abs
/**
* 歌词 Created by wcy on 2015/11/9.
*/
@SuppressLint("StaticFieldLeak")
class CoverLrcView @JvmOverloads constructor(
context: Context?,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val mLrcEntryList: MutableList<LrcEntry> = ArrayList()
private val mLrcPaint = TextPaint()
private val mTimePaint = TextPaint()
private var mTimeFontMetrics: Paint.FontMetrics? = null
private var mPlayDrawable: Drawable? = null
private var mDividerHeight = 0f
private var mAnimationDuration: Long = 0
private var mNormalTextColor = 0
private var mNormalTextSize = 0f
private var mCurrentTextColor = 0
private var mCurrentTextSize = 0f
private var mTimelineTextColor = 0
private var mTimelineColor = 0
private var mTimeTextColor = 0
private var mDrawableWidth = 0
private var mTimeTextWidth = 0
private var mDefaultLabel: String? = null
private var mLrcPadding = 0f
private var mOnPlayClickListener: OnPlayClickListener? = null
private var mOnFlingXListener: OnFlingXListener? = null
private var mAnimator: ValueAnimator? = null
private var mGestureDetector: GestureDetector? = null
private var mScroller: Scroller? = null
private var mOffset = 0f
private var mCurrentLine = 0
private var flag: Any? = null
private var isShowTimeline = false
private var isTouching = false
private var isFling = false
private var mTextGravity // 歌词显示位置,靠左/居中/靠右
= 0
private val hideTimelineRunnable = Runnable {
if (hasLrc() && isShowTimeline) {
isShowTimeline = false
smoothScrollTo(mCurrentLine)
}
}
/**
* 手势监听器
*/
private val mSimpleOnGestureListener: SimpleOnGestureListener =
object : SimpleOnGestureListener() {
override fun onDown(e: MotionEvent): Boolean {
if (hasLrc() && mOnPlayClickListener != null) {
if (mOffset != getOffset(0)) {
parent.requestDisallowInterceptTouchEvent(true)
}
mScroller!!.forceFinished(true)
removeCallbacks(hideTimelineRunnable)
isTouching = true
isShowTimeline = true
invalidate()
return true
}
return super.onDown(e)
}
override fun onScroll(
e1: MotionEvent,
e2: MotionEvent,
distanceX: Float,
distanceY: Float
): Boolean {
if (mOffset == getOffset(0) && distanceY < 0F) {
return super.onScroll(e1, e2, distanceX, distanceY)
}
if (hasLrc()) {
mOffset += -distanceY
mOffset = mOffset.coerceAtMost(getOffset(0))
mOffset = mOffset.coerceAtLeast(getOffset(mLrcEntryList.size - 1))
invalidate()
parent.requestDisallowInterceptTouchEvent(true)
return true
}
return super.onScroll(e1, e2, distanceX, distanceY)
}
override fun onFling(
e1: MotionEvent,
e2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
if (mOnFlingXListener != null && abs(velocityX) > abs(velocityY)) {
return mOnFlingXListener!!.onFlingX(velocityX)
}
if (hasLrc()) {
mScroller!!.fling(
0,
mOffset.toInt(),
0,
velocityY.toInt(),
0,
0,
getOffset(mLrcEntryList.size - 1).toInt(),
getOffset(0).toInt()
)
isFling = true
return true
}
return super.onFling(e1, e2, velocityX, velocityY)
}
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
if (hasLrc()
&& isShowTimeline
&& mPlayDrawable!!.bounds.contains(e.x.toInt(), e.y.toInt())
) {
val centerLine = centerLine
val centerLineTime = mLrcEntryList[centerLine].time
// onPlayClick 消费了才更新 UI
if (mOnPlayClickListener != null && mOnPlayClickListener!!.onPlayClick(
centerLineTime
)
) {
isShowTimeline = false
removeCallbacks(hideTimelineRunnable)
mCurrentLine = centerLine
invalidate()
return true
}
}
return super.onSingleTapConfirmed(e)
}
}
private fun init(attrs: AttributeSet?) {
val ta = context.obtainStyledAttributes(attrs, R.styleable.LrcView)
mCurrentTextSize = ta.getDimension(
R.styleable.LrcView_lrcTextSize, resources.getDimension(R.dimen.lrc_text_size)
)
mNormalTextSize = ta.getDimension(
R.styleable.LrcView_lrcNormalTextSize,
resources.getDimension(R.dimen.lrc_text_size)
)
if (mNormalTextSize == 0f) {
mNormalTextSize = mCurrentTextSize
}
mDividerHeight = ta.getDimension(
R.styleable.LrcView_lrcDividerHeight,
resources.getDimension(R.dimen.lrc_divider_height)
)
val defDuration = resources.getInteger(R.integer.lrc_animation_duration)
mAnimationDuration =
ta.getInt(R.styleable.LrcView_lrcAnimationDuration, defDuration).toLong()
mAnimationDuration =
if (mAnimationDuration < 0) defDuration.toLong() else mAnimationDuration
mNormalTextColor = ta.getColor(
R.styleable.LrcView_lrcNormalTextColor,
ContextCompat.getColor(context, R.color.lrc_normal_text_color)
)
mCurrentTextColor = ta.getColor(
R.styleable.LrcView_lrcCurrentTextColor,
ContextCompat.getColor(context, R.color.lrc_current_text_color)
)
mTimelineTextColor = ta.getColor(
R.styleable.LrcView_lrcTimelineTextColor,
ContextCompat.getColor(context, R.color.lrc_timeline_text_color)
)
mDefaultLabel = ta.getString(R.styleable.LrcView_lrcLabel)
mDefaultLabel =
if (TextUtils.isEmpty(mDefaultLabel)) context.getString(R.string.empty) else mDefaultLabel
mLrcPadding = ta.getDimension(R.styleable.LrcView_lrcPadding, 0f)
mTimelineColor = ta.getColor(
R.styleable.LrcView_lrcTimelineColor,
ContextCompat.getColor(context, R.color.lrc_timeline_color)
)
val timelineHeight = ta.getDimension(
R.styleable.LrcView_lrcTimelineHeight,
resources.getDimension(R.dimen.lrc_timeline_height)
)
mPlayDrawable = ta.getDrawable(R.styleable.LrcView_lrcPlayDrawable)
mPlayDrawable =
if (mPlayDrawable == null) ContextCompat.getDrawable(
context,
R.drawable.ic_play_arrow
) else mPlayDrawable
mTimeTextColor = ta.getColor(
R.styleable.LrcView_lrcTimeTextColor,
ContextCompat.getColor(context, R.color.lrc_time_text_color)
)
val timeTextSize = ta.getDimension(
R.styleable.LrcView_lrcTimeTextSize,
resources.getDimension(R.dimen.lrc_time_text_size)
)
mTextGravity = ta.getInteger(R.styleable.LrcView_lrcTextGravity, LrcEntry.GRAVITY_CENTER)
ta.recycle()
mDrawableWidth = resources.getDimension(R.dimen.lrc_drawable_width).toInt()
mTimeTextWidth = resources.getDimension(R.dimen.lrc_time_width).toInt()
mLrcPaint.isAntiAlias = true
mLrcPaint.textSize = mCurrentTextSize
mLrcPaint.textAlign = Paint.Align.LEFT
mTimePaint.isAntiAlias = true
mTimePaint.textSize = timeTextSize
mTimePaint.textAlign = Paint.Align.CENTER
mTimePaint.strokeWidth = timelineHeight
mTimePaint.strokeCap = Paint.Cap.ROUND
mTimeFontMetrics = mTimePaint.fontMetrics
mGestureDetector = GestureDetector(context, mSimpleOnGestureListener)
mGestureDetector!!.setIsLongpressEnabled(false)
mScroller = Scroller(context)
}
/** 设置非当前行歌词字体颜色 */
fun setNormalColor(normalColor: Int) {
mNormalTextColor = normalColor
postInvalidate()
}
/** 普通歌词文本字体大小 */
fun setNormalTextSize(size: Float) {
mNormalTextSize = size
}
/** 当前歌词文本字体大小 */
fun setCurrentTextSize(size: Float) {
mCurrentTextSize = size
}
/** 设置当前行歌词的字体颜色 */
fun setCurrentColor(currentColor: Int) {
mCurrentTextColor = currentColor
postInvalidate()
}
/** 设置拖动歌词时选中歌词的字体颜色 */
fun setTimelineTextColor(timelineTextColor: Int) {
mTimelineTextColor = timelineTextColor
postInvalidate()
}
/** 设置拖动歌词时时间线的颜色 */
fun setTimelineColor(timelineColor: Int) {
mTimelineColor = timelineColor
postInvalidate()
}
/** 设置拖动歌词时右侧时间字体颜色 */
fun setTimeTextColor(timeTextColor: Int) {
mTimeTextColor = timeTextColor
postInvalidate()
}
/**
* 设置歌词是否允许拖动
*
* @param draggable 是否允许拖动
* @param onPlayClickListener 设置歌词拖动后播放按钮点击监听器如果允许拖动则不能为 null
*/
fun setDraggable(draggable: Boolean, onPlayClickListener: OnPlayClickListener?) {
mOnPlayClickListener = if (draggable) {
requireNotNull(onPlayClickListener) { "if draggable == true, onPlayClickListener must not be null" }
onPlayClickListener
} else {
null
}
}
/**
* 设置播放按钮点击监听器
*
* @param onPlayClickListener 如果为非 null 则激活歌词拖动功能否则将将禁用歌词拖动功能
*/
@Deprecated("use {@link #setDraggable(boolean, OnPlayClickListener)} instead")
fun setOnPlayClickListener(onPlayClickListener: OnPlayClickListener?) {
mOnPlayClickListener = onPlayClickListener
}
fun setOnFlingXListener(onFlingXListener: OnFlingXListener) {
mOnFlingXListener = onFlingXListener
}
/** 设置歌词为空时屏幕中央显示的文字,如“暂无歌词” */
fun setLabel(label: String?) {
runOnUi {
mDefaultLabel = label
invalidate()
}
}
/**
* 加载歌词文件
*
* @param lrcFile 歌词文件
*/
fun loadLrc(lrcFile: File) {
loadLrc(lrcFile, null)
}
/**
* 加载双语歌词文件两种语言的歌词时间戳需要一致
*
* @param mainLrcFile 第一种语言歌词文件
* @param secondLrcFile 第二种语言歌词文件
*/
private fun loadLrc(mainLrcFile: File, secondLrcFile: File?) {
runOnUi {
reset()
val sb = StringBuilder("file://")
sb.append(mainLrcFile.path)
if (secondLrcFile != null) {
sb.append("#").append(secondLrcFile.path)
}
val flag = sb.toString()
this.flag = flag
object : AsyncTask<File?, Int?, List<LrcEntry>>() {
override fun doInBackground(vararg params: File?): List<LrcEntry>? {
return LrcUtils.parseLrc(params)
}
override fun onPostExecute(lrcEntries: List<LrcEntry>) {
if (flag == flag) {
onLrcLoaded(lrcEntries)
this@CoverLrcView.flag = null
}
}
}.execute(mainLrcFile, secondLrcFile)
}
}
/**
* 加载歌词文本
*
* @param lrcText 歌词文本
*/
fun loadLrc(lrcText: String?) {
loadLrc(lrcText, null)
}
/**
* 加载双语歌词文本两种语言的歌词时间戳需要一致
*
* @param mainLrcText 第一种语言歌词文本
* @param secondLrcText 第二种语言歌词文本
*/
private fun loadLrc(mainLrcText: String?, secondLrcText: String?) {
runOnUi {
reset()
val sb = StringBuilder("file://")
sb.append(mainLrcText)
if (secondLrcText != null) {
sb.append("#").append(secondLrcText)
}
val flag = sb.toString()
this.flag = flag
object : AsyncTask<String?, Int?, List<LrcEntry>>() {
override fun doInBackground(vararg params: String?): List<LrcEntry>? {
return LrcUtils.parseLrc(params)
}
override fun onPostExecute(lrcEntries: List<LrcEntry>) {
if (flag == flag) {
onLrcLoaded(lrcEntries)
this@CoverLrcView.flag = null
}
}
}.execute(mainLrcText, secondLrcText)
}
}
/**
* 加载在线歌词默认使用 utf-8 编码
*
* @param lrcUrl 歌词文件的网络地址
*/
@JvmOverloads
fun loadLrcByUrl(lrcUrl: String, charset: String? = "utf-8") {
val flag = "url://$lrcUrl"
this.flag = flag
object : AsyncTask<String?, Int?, String>() {
override fun doInBackground(vararg params: String?): String? {
return LrcUtils.getContentFromNetwork(params[0], params[1])
}
override fun onPostExecute(lrcText: String) {
if (flag == flag) {
loadLrc(lrcText)
}
}
}.execute(lrcUrl, charset)
}
/**
* 歌词是否有效
*
* @return true如果歌词有效否则false
*/
fun hasLrc(): Boolean {
return mLrcEntryList.isNotEmpty()
}
/**
* 刷新歌词
*
* @param time 当前播放时间
*/
fun updateTime(time: Long) {
runOnUi {
if (!hasLrc()) {
return@runOnUi
}
val line = findShowLine(time)
if (line != mCurrentLine) {
mCurrentLine = line
if (!isShowTimeline) {
smoothScrollTo(line)
} else {
invalidate()
}
}
}
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
if (changed) {
initPlayDrawable()
initEntryList()
if (hasLrc()) {
smoothScrollTo(mCurrentLine, 0L)
}
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val centerY = height / 2
// 无歌词文件
if (!hasLrc()) {
mLrcPaint.color = mCurrentTextColor
@SuppressLint("DrawAllocation") val staticLayout = StaticLayout(
mDefaultLabel,
mLrcPaint,
lrcWidth.toInt(),
Layout.Alignment.ALIGN_CENTER,
1f,
0f,
false
)
drawText(canvas, staticLayout, centerY.toFloat())
return
}
val centerLine = centerLine
if (isShowTimeline) {
mPlayDrawable?.draw(canvas)
mTimePaint.color = mTimeTextColor
val timeText = LrcUtils.formatTime(mLrcEntryList[centerLine].time)
val timeX = (width - mTimeTextWidth / 2).toFloat()
val timeY = centerY - (mTimeFontMetrics!!.descent + mTimeFontMetrics!!.ascent) / 2
canvas.drawText(timeText, timeX, timeY, mTimePaint)
}
canvas.translate(0f, mOffset)
var y = 0f
for (i in mLrcEntryList.indices) {
if (i > 0) {
y += ((mLrcEntryList[i - 1].height + mLrcEntryList[i].height shr 1)
+ mDividerHeight)
}
if (i == mCurrentLine) {
mLrcPaint.textSize = mCurrentTextSize
mLrcPaint.color = mCurrentTextColor
} else if (isShowTimeline && i == centerLine) {
mLrcPaint.color = mTimelineTextColor
} else {
mLrcPaint.textSize = mNormalTextSize
mLrcPaint.color = mNormalTextColor
}
drawText(canvas, mLrcEntryList[i].staticLayout, y)
}
}
/**
* 画一行歌词
*
* @param y 歌词中心 Y 坐标
*/
private fun drawText(canvas: Canvas, staticLayout: StaticLayout, y: Float) {
canvas.save()
canvas.translate(mLrcPadding, y - (staticLayout.height shr 1))
staticLayout.draw(canvas)
canvas.restore()
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_UP
|| event.action == MotionEvent.ACTION_CANCEL
) {
isTouching = false
if (hasLrc() && !isFling) {
adjustCenter()
postDelayed(hideTimelineRunnable, TIMELINE_KEEP_TIME)
}
}
return mGestureDetector!!.onTouchEvent(event)
}
override fun computeScroll() {
if (mScroller!!.computeScrollOffset()) {
mOffset = mScroller!!.currY.toFloat()
invalidate()
}
if (isFling && mScroller!!.isFinished) {
isFling = false
if (hasLrc() && !isTouching) {
adjustCenter()
postDelayed(hideTimelineRunnable, TIMELINE_KEEP_TIME)
}
}
}
override fun onDetachedFromWindow() {
removeCallbacks(hideTimelineRunnable)
super.onDetachedFromWindow()
}
private fun onLrcLoaded(entryList: List<LrcEntry>?) {
if (entryList != null && entryList.isNotEmpty()) {
mLrcEntryList.addAll(entryList)
}
mLrcEntryList.sort()
initEntryList()
invalidate()
}
private fun initPlayDrawable() {
val l = (mTimeTextWidth - mDrawableWidth) / 2
val t = height / 2 - mDrawableWidth / 2
val r = l + mDrawableWidth
val b = t + mDrawableWidth
mPlayDrawable!!.setBounds(l, t, r, b)
}
private fun initEntryList() {
if (!hasLrc() || width == 0) {
return
}
for (lrcEntry in mLrcEntryList) {
lrcEntry.init(mLrcPaint, lrcWidth.toInt(), mTextGravity)
}
mOffset = (height / 2).toFloat()
}
fun reset() {
endAnimation()
mScroller!!.forceFinished(true)
isShowTimeline = false
isTouching = false
isFling = false
removeCallbacks(hideTimelineRunnable)
mLrcEntryList.clear()
mOffset = 0f
mCurrentLine = 0
invalidate()
}
/** 将中心行微调至正中心 */
private fun adjustCenter() {
smoothScrollTo(centerLine, ADJUST_DURATION)
}
/** 滚动到某一行 */
/** 滚动到某一行 */
private fun smoothScrollTo(line: Int, duration: Long = mAnimationDuration) {
val offset = getOffset(line)
endAnimation()
mAnimator = ValueAnimator.ofFloat(mOffset, offset).apply {
this.duration = duration
interpolator = LinearInterpolator()
addUpdateListener { animation: ValueAnimator ->
mOffset = animation.animatedValue as Float
invalidate()
}
LrcUtils.resetDurationScale()
start()
}
}
/** 结束滚动动画 */
private fun endAnimation() {
if (mAnimator != null && mAnimator!!.isRunning) {
mAnimator!!.end()
}
}
/** 二分法查找当前时间应该显示的行数(最后一个 <= time 的行数) */
private fun findShowLine(time: Long): Int {
var left = 0
var right = mLrcEntryList.size
while (left <= right) {
val middle = (left + right) / 2
val middleTime = mLrcEntryList[middle].time
if (time < middleTime) {
right = middle - 1
} else {
if (middle + 1 >= mLrcEntryList.size || time < mLrcEntryList[middle + 1].time) {
return middle
}
left = middle + 1
}
}
return 0
}
/** 获取当前在视图中央的行数 */
private val centerLine: Int
get() {
var centerLine = 0
var minDistance = Float.MAX_VALUE
for (i in mLrcEntryList.indices) {
if (abs(mOffset - getOffset(i)) < minDistance) {
minDistance = abs(mOffset - getOffset(i))
centerLine = i
}
}
return centerLine
}
/** 获取歌词距离视图顶部的距离 采用懒加载方式 */
private fun getOffset(line: Int): Float {
if (mLrcEntryList.isEmpty()) return 0F
if (mLrcEntryList[line].offset == Float.MIN_VALUE) {
var offset = (height / 2).toFloat()
for (i in 1..line) {
offset -= ((mLrcEntryList[i - 1].height + mLrcEntryList[i].height shr 1)
+ mDividerHeight)
}
mLrcEntryList[line].offset = offset
}
return mLrcEntryList[line].offset
}
/** 获取歌词宽度 */
private val lrcWidth: Float
get() = width - mLrcPadding * 2
/** 在主线程中运行 */
private fun runOnUi(r: Runnable) {
if (Looper.myLooper() == Looper.getMainLooper()) {
r.run()
} else {
post(r)
}
}
/** 播放按钮点击监听器,点击后应该跳转到指定播放位置 */
fun interface OnPlayClickListener {
/**
* 播放按钮被点击应该跳转到指定播放位置
*
* @return 是否成功消费该事件如果成功消费则会更新UI
*/
fun onPlayClick(time: Long): Boolean
}
fun interface OnFlingXListener {
fun onFlingX(velocityX: Float): Boolean
}
companion object {
private const val ADJUST_DURATION: Long = 100
private const val TIMELINE_KEEP_TIME = 4 * DateUtils.SECOND_IN_MILLIS
}
init {
init(attrs)
}
}

View file

@ -72,6 +72,7 @@ interface Repository {
suspend fun genresHome(): Home suspend fun genresHome(): Home
suspend fun playlists(): Home suspend fun playlists(): Home
suspend fun homeSections(): List<Home> suspend fun homeSections(): List<Home>
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
suspend fun homeSectionsFlow(): Flow<Result<List<Home>>> suspend fun homeSectionsFlow(): Flow<Result<List<Home>>>
suspend fun playlist(playlistId: Long): Playlist suspend fun playlist(playlistId: Long): Playlist
@ -106,6 +107,7 @@ interface Repository {
suspend fun searchArtists(query: String): List<Artist> suspend fun searchArtists(query: String): List<Artist>
suspend fun searchSongs(query: String): List<Song> suspend fun searchSongs(query: String): List<Song>
suspend fun searchAlbums(query: String): List<Album> suspend fun searchAlbums(query: String): List<Album>
suspend fun isSongFavorite(songId: Long): Boolean
fun getSongByGenre(genreId: Long): Song fun getSongByGenre(genreId: Long): Song
} }
@ -133,6 +135,9 @@ class RealRepository(
override suspend fun searchAlbums(query: String): List<Album> = albumRepository.albums(query) override suspend fun searchAlbums(query: String): List<Album> = albumRepository.albums(query)
override suspend fun isSongFavorite(songId: Long): Boolean =
roomRepository.isSongFavorite(context, songId)
override fun getSongByGenre(genreId: Long): Song = genreRepository.song(genreId) override fun getSongByGenre(genreId: Long): Song = genreRepository.song(genreId)
override suspend fun searchArtists(query: String): List<Artist> = override suspend fun searchArtists(query: String): List<Artist> =
@ -150,7 +155,8 @@ class RealRepository(
override suspend fun artistById(artistId: Long): Artist = artistRepository.artist(artistId) override suspend fun artistById(artistId: Long): Artist = artistRepository.artist(artistId)
override suspend fun albumArtistByName(name: String): Artist = artistRepository.albumArtist(name) override suspend fun albumArtistByName(name: String): Artist =
artistRepository.albumArtist(name)
override suspend fun recentArtists(): List<Artist> = lastAddedRepository.recentArtists() override suspend fun recentArtists(): List<Artist> = lastAddedRepository.recentArtists()

View file

@ -1,7 +1,9 @@
package code.name.monkey.retromusic.repository package code.name.monkey.retromusic.repository
import android.content.Context
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.db.* import code.name.monkey.retromusic.db.*
import code.name.monkey.retromusic.helper.SortOrder.PlaylistSortOrder.Companion.PLAYLIST_A_Z import code.name.monkey.retromusic.helper.SortOrder.PlaylistSortOrder.Companion.PLAYLIST_A_Z
import code.name.monkey.retromusic.helper.SortOrder.PlaylistSortOrder.Companion.PLAYLIST_SONG_COUNT import code.name.monkey.retromusic.helper.SortOrder.PlaylistSortOrder.Companion.PLAYLIST_SONG_COUNT
@ -45,6 +47,7 @@ interface RoomRepository {
suspend fun insertBlacklistPathAsync(blackListStoreEntity: BlackListStoreEntity) suspend fun insertBlacklistPathAsync(blackListStoreEntity: BlackListStoreEntity)
suspend fun blackListPaths(): List<BlackListStoreEntity> suspend fun blackListPaths(): List<BlackListStoreEntity>
suspend fun deleteSongs(songs: List<Song>) suspend fun deleteSongs(songs: List<Song>)
suspend fun isSongFavorite(context: Context, songId: Long): Boolean
} }
class RealRoomRepository( class RealRoomRepository(
@ -60,7 +63,7 @@ class RealRoomRepository(
@WorkerThread @WorkerThread
override suspend fun checkPlaylistExists(playlistName: String): List<PlaylistEntity> = override suspend fun checkPlaylistExists(playlistName: String): List<PlaylistEntity> =
playlistDao.isPlaylistExists(playlistName) playlistDao.playlist(playlistName)
@WorkerThread @WorkerThread
override suspend fun playlists(): List<PlaylistEntity> = playlistDao.playlists() override suspend fun playlists(): List<PlaylistEntity> = playlistDao.playlists()
@ -111,12 +114,13 @@ class RealRoomRepository(
} }
override suspend fun favoritePlaylist(favorite: String): PlaylistEntity { override suspend fun favoritePlaylist(favorite: String): PlaylistEntity {
val playlist: PlaylistEntity? = playlistDao.isPlaylistExists(favorite).firstOrNull() val playlist: PlaylistEntity? = playlistDao.playlist(favorite).firstOrNull()
return if (playlist != null) { return if (playlist != null) {
playlist playlist
} else { } else {
println("Playlist Created")
createPlaylist(PlaylistEntity(playlistName = favorite)) createPlaylist(PlaylistEntity(playlistName = favorite))
playlistDao.isPlaylistExists(favorite).first() playlistDao.playlist(favorite).first()
} }
} }
@ -145,12 +149,12 @@ class RealRoomRepository(
override fun favoritePlaylistLiveData(favorite: String): LiveData<List<SongEntity>> = override fun favoritePlaylistLiveData(favorite: String): LiveData<List<SongEntity>> =
playlistDao.favoritesSongsLiveData( playlistDao.favoritesSongsLiveData(
playlistDao.isPlaylistExists(favorite).first().playListId playlistDao.playlist(favorite).first().playListId
) )
override suspend fun favoritePlaylistSongs(favorite: String): List<SongEntity> = override suspend fun favoritePlaylistSongs(favorite: String): List<SongEntity> =
if (playlistDao.isPlaylistExists(favorite).isNotEmpty()) playlistDao.favoritesSongs( if (playlistDao.playlist(favorite).isNotEmpty()) playlistDao.favoritesSongs(
playlistDao.isPlaylistExists(favorite).first().playListId playlistDao.playlist(favorite).first().playListId
) else emptyList() ) else emptyList()
override suspend fun insertSongInPlayCount(playCountEntity: PlayCountEntity) = override suspend fun insertSongInPlayCount(playCountEntity: PlayCountEntity) =
@ -192,4 +196,12 @@ class RealRoomRepository(
blackListStoreDao.deleteBlacklistPath(blackListStoreEntity) blackListStoreDao.deleteBlacklistPath(blackListStoreEntity)
override suspend fun clearBlacklist() = blackListStoreDao.clearBlacklist() override suspend fun clearBlacklist() = blackListStoreDao.clearBlacklist()
override suspend fun isSongFavorite(context: Context, songId: Long): Boolean {
return playlistDao.isSongExistsInPlaylist(
playlistDao.playlist(context.getString(R.string.favorites)).firstOrNull()?.playListId
?: -1,
songId
).isNotEmpty()
}
} }

View file

@ -24,6 +24,8 @@ import static code.name.monkey.retromusic.ConstantsKt.COLORED_NOTIFICATION;
import static code.name.monkey.retromusic.ConstantsKt.CROSS_FADE_DURATION; import static code.name.monkey.retromusic.ConstantsKt.CROSS_FADE_DURATION;
import static code.name.monkey.retromusic.ConstantsKt.TOGGLE_HEADSET; import static code.name.monkey.retromusic.ConstantsKt.TOGGLE_HEADSET;
import static code.name.monkey.retromusic.service.AudioFader.startFadeAnimator; import static code.name.monkey.retromusic.service.AudioFader.startFadeAnimator;
import static code.name.monkey.retromusic.service.notification.PlayingNotification.NOTIFY_MODE_BACKGROUND;
import static code.name.monkey.retromusic.service.notification.PlayingNotification.NOTIFY_MODE_FOREGROUND;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
@ -369,6 +371,7 @@ public class MusicService extends MediaBrowserServiceCompat
private PowerManager.WakeLock wakeLock; private PowerManager.WakeLock wakeLock;
private NotificationManager notificationManager; private NotificationManager notificationManager;
private boolean isForeground = false; private boolean isForeground = false;
private int notifyMode = NOTIFY_MODE_BACKGROUND;
private static Bitmap copy(Bitmap bitmap) { private static Bitmap copy(Bitmap bitmap) {
Bitmap.Config config = bitmap.getConfig(); Bitmap.Config config = bitmap.getConfig();
@ -938,8 +941,9 @@ public class MusicService extends MediaBrowserServiceCompat
updateNotification(); updateNotification();
break; break;
case CLASSIC_NOTIFICATION: case CLASSIC_NOTIFICATION:
initNotification();
updateNotification(); updateNotification();
playingNotification.setPlaying(isPlaying());
playingNotification.updateMetadata(getCurrentSong(), this::startForegroundOrNotify);
break; break;
case TOGGLE_HEADSET: case TOGGLE_HEADSET:
registerHeadsetEvents(); registerHeadsetEvents();
@ -1456,9 +1460,18 @@ public class MusicService extends MediaBrowserServiceCompat
} }
private Unit startForegroundOrNotify() { private Unit startForegroundOrNotify() {
if (!isForeground) { int newNotifyMode = isPlaying() ? NOTIFY_MODE_FOREGROUND : NOTIFY_MODE_BACKGROUND;
if (notifyMode != newNotifyMode && newNotifyMode == NOTIFY_MODE_BACKGROUND) {
// This makes the notification dismissible
// We can't call stopForeground(false) on A12 though, which may result in crashes
// when we call startForeground after that e.g. when Alarm goes off,
if (Build.VERSION.SDK_INT < VERSION_CODES.S) stopForeground(false);
}
if (newNotifyMode == NOTIFY_MODE_FOREGROUND) {
// Specify that this is a media service, if supported. // Specify that this is a media service, if supported.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (VersionUtils.hasQ()) {
startForeground( startForeground(
PlayingNotification.NOTIFICATION_ID, playingNotification.build(), PlayingNotification.NOTIFICATION_ID, playingNotification.build(),
ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
@ -1474,6 +1487,7 @@ public class MusicService extends MediaBrowserServiceCompat
PlayingNotification.NOTIFICATION_ID, playingNotification.build() PlayingNotification.NOTIFICATION_ID, playingNotification.build()
); );
} }
notifyMode = newNotifyMode;
return Unit.INSTANCE; return Unit.INSTANCE;
} }

View file

@ -37,6 +37,8 @@ abstract class PlayingNotification(context: Context) :
const val NOTIFICATION_CONTROLS_SIZE_MULTIPLIER = 1.0f const val NOTIFICATION_CONTROLS_SIZE_MULTIPLIER = 1.0f
internal const val NOTIFICATION_CHANNEL_ID = "playing_notification" internal const val NOTIFICATION_CHANNEL_ID = "playing_notification"
const val NOTIFICATION_ID = 1 const val NOTIFICATION_ID = 1
const val NOTIFY_MODE_FOREGROUND = 1
const val NOTIFY_MODE_BACKGROUND = 0
@RequiresApi(26) @RequiresApi(26)

View file

@ -30,8 +30,6 @@ import androidx.media.app.NotificationCompat.MediaStyle
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.R
import code.name.monkey.retromusic.activities.MainActivity import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.db.PlaylistEntity
import code.name.monkey.retromusic.db.toSongEntity
import code.name.monkey.retromusic.glide.GlideApp import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
@ -163,6 +161,7 @@ class PlayingNotificationImpl(
onUpdate() onUpdate()
} }
}) })
updateFavorite(song, onUpdate)
} }
private fun buildPlayAction(isPlaying: Boolean): NotificationCompat.Action { private fun buildPlayAction(isPlaying: Boolean): NotificationCompat.Action {
@ -185,17 +184,28 @@ class PlayingNotificationImpl(
).build() ).build()
} }
private fun buildDismissAction(): NotificationCompat.Action {
return NotificationCompat.Action.Builder(
R.drawable.ic_close,
context.getString(R.string.customactivityoncrash_error_activity_error_details_close),
retrievePlaybackAction(ACTION_QUIT)
).build()
}
override fun setPlaying(isPlaying: Boolean) { override fun setPlaying(isPlaying: Boolean) {
mActions[2] = buildPlayAction(isPlaying) mActions[2] = buildPlayAction(isPlaying)
// Show dismiss action if we are not playing but only for A12+, as we can't call stopForeground(false)
// on A12 which would result in crashes when we call startForeground after that
if (!isPlaying) {
addAction(buildDismissAction())
} else {
if (mActions.size == 5) mActions.removeAt(4)
}
} }
override fun updateFavorite(song: Song, onUpdate: () -> Unit) { override fun updateFavorite(song: Song, onUpdate: () -> Unit) {
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
val playlist: PlaylistEntity = MusicUtil.repository.favoritePlaylist() val isFavorite = MusicUtil.repository.isSongFavorite(song.id)
val isFavorite = if (playlist != null) {
val songEntity = song.toSongEntity(playlist.playListId)
MusicUtil.repository.isFavoriteSong(songEntity).isNotEmpty()
} else false
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
mActions[0] = buildFavoriteAction(isFavorite) mActions[0] = buildFavoriteAction(isFavorite)
onUpdate() onUpdate()

View file

@ -15,12 +15,7 @@
package code.name.monkey.retromusic.util package code.name.monkey.retromusic.util
import android.app.Activity import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.net.Uri
import code.name.monkey.retromusic.R
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.play.core.review.ReviewManagerFactory import com.google.android.play.core.review.ReviewManagerFactory
object AppRater { object AppRater {

View file

@ -1,189 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.util;
import android.util.Base64;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import code.name.monkey.retromusic.model.Song;
/**
* Created by hefuyi on 2016/11/8.
*/
public class LyricUtil {
private static final String lrcRootPath =
android.os.Environment.getExternalStorageDirectory().toString() + "/RetroMusic/lyrics/";
private static final String TAG = "LyricUtil";
@Nullable
public static File writeLrcToLoc(
@NonNull String title, @NonNull String artist, @NonNull String lrcContext) {
FileWriter writer = null;
try {
File file = new File(getLrcPath(title, artist));
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
writer = new FileWriter(getLrcPath(title, artist));
writer.write(lrcContext);
return file;
} catch (IOException e) {
e.printStackTrace();
return null;
} finally {
try {
if (writer != null) writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//So in Retro, Lrc file can be same folder as Music File or in RetroMusic Folder
// In this case we pass location of the file and Contents to write to file
public static void writeLrc(@NonNull Song song, @NonNull String lrcContext) {
FileWriter writer = null;
File location;
try {
if (isLrcOriginalFileExist(song.getData())) {
location = getLocalLyricOriginalFile(song.getData());
} else if (isLrcFileExist(song.getTitle(), song.getArtistName())) {
location = getLocalLyricFile(song.getTitle(), song.getArtistName());
} else {
location = new File(getLrcPath(song.getTitle(), song.getArtistName()));
if (!location.getParentFile().exists()) {
location.getParentFile().mkdirs();
}
}
writer = new FileWriter(location);
writer.write(lrcContext);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (writer != null) writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static boolean deleteLrcFile(@NonNull String title, @NonNull String artist) {
File file = new File(getLrcPath(title, artist));
return file.delete();
}
public static boolean isLrcFileExist(@NonNull String title, @NonNull String artist) {
File file = new File(getLrcPath(title, artist));
return file.exists();
}
public static boolean isLrcOriginalFileExist(@NonNull String path) {
File file = new File(getLrcOriginalPath(path));
return file.exists();
}
@Nullable
public static File getLocalLyricFile(@NonNull String title, @NonNull String artist) {
File file = new File(getLrcPath(title, artist));
if (file.exists()) {
return file;
} else {
return null;
}
}
@Nullable
public static File getLocalLyricOriginalFile(@NonNull String path) {
File file = new File(getLrcOriginalPath(path));
if (file.exists()) {
return file;
} else {
return null;
}
}
private static String getLrcPath(String title, String artist) {
return lrcRootPath + title + " - " + artist + ".lrc";
}
private static String getLrcOriginalPath(String filePath) {
return filePath.replace(filePath.substring(filePath.lastIndexOf(".") + 1), "lrc");
}
@NonNull
public static String decryptBASE64(@NonNull String str) {
if (str == null || str.length() == 0) {
return null;
}
byte[] encode = str.getBytes(StandardCharsets.UTF_8);
// base64 解密
return new String(
Base64.decode(encode, 0, encode.length, Base64.DEFAULT), StandardCharsets.UTF_8);
}
@NonNull
public static String getStringFromFile(@NonNull String title, @NonNull String artist)
throws Exception {
File file = new File(getLrcPath(title, artist));
FileInputStream fin = new FileInputStream(file);
String ret = convertStreamToString(fin);
fin.close();
return ret;
}
private static String convertStreamToString(InputStream is) throws Exception {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
reader.close();
return sb.toString();
}
public static String getStringFromLrc(File file) {
try {
BufferedReader reader = new BufferedReader(new FileReader(file));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
reader.close();
return sb.toString();
} catch (Exception e) {
Log.i("Error", "Error Occurred");
}
return "";
}
}

View file

@ -0,0 +1,188 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.util
import android.os.Environment
import android.util.Log
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.model.lyrics.AbsSynchronizedLyrics
import org.jaudiotagger.audio.AudioFileIO
import org.jaudiotagger.tag.FieldKey
import java.io.*
/**
* Created by hefuyi on 2016/11/8.
*/
object LyricUtil {
private val lrcRootPath =
Environment.getExternalStorageDirectory().toString() + "/RetroMusic/lyrics/"
private const val TAG = "LyricUtil"
fun writeLrcToLoc(
title: String, artist: String, lrcContext: String
): File? {
var writer: FileWriter? = null
return try {
val file = File(getLrcPath(title, artist))
if (file.parentFile?.exists() != true) {
file.parentFile?.mkdirs()
}
writer = FileWriter(getLrcPath(title, artist))
writer.write(lrcContext)
file
} catch (e: IOException) {
e.printStackTrace()
null
} finally {
try {
writer?.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
//So in Retro, Lrc file can be same folder as Music File or in RetroMusic Folder
// In this case we pass location of the file and Contents to write to file
fun writeLrc(song: Song, lrcContext: String) {
var writer: FileWriter? = null
val location: File?
try {
if (isLrcOriginalFileExist(song.data)) {
location = getLocalLyricOriginalFile(song.data)
} else if (isLrcFileExist(song.title, song.artistName)) {
location = getLocalLyricFile(song.title, song.artistName)
} else {
location = File(getLrcPath(song.title, song.artistName))
if (location.parentFile?.exists() != true) {
location.parentFile?.mkdirs()
}
}
writer = FileWriter(location)
writer.write(lrcContext)
} catch (e: IOException) {
e.printStackTrace()
} finally {
try {
writer?.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
fun deleteLrcFile(title: String, artist: String): Boolean {
val file = File(getLrcPath(title, artist))
return file.delete()
}
private fun isLrcFileExist(title: String, artist: String): Boolean {
val file = File(getLrcPath(title, artist))
return file.exists()
}
private fun isLrcOriginalFileExist(path: String): Boolean {
val file = File(getLrcOriginalPath(path))
return file.exists()
}
private fun getLocalLyricFile(title: String, artist: String): File? {
val file = File(getLrcPath(title, artist))
return if (file.exists()) {
file
} else {
null
}
}
private fun getLocalLyricOriginalFile(path: String): File? {
val file = File(getLrcOriginalPath(path))
return if (file.exists()) {
file
} else {
null
}
}
private fun getLrcPath(title: String, artist: String): String {
return "$lrcRootPath$title - $artist.lrc"
}
private fun getLrcOriginalPath(filePath: String): String {
return filePath.replace(filePath.substring(filePath.lastIndexOf(".") + 1), "lrc")
}
@Throws(Exception::class)
fun getStringFromFile(title: String, artist: String): String {
val file = File(getLrcPath(title, artist))
val fin = FileInputStream(file)
val ret = convertStreamToString(fin)
fin.close()
return ret
}
@Throws(Exception::class)
private fun convertStreamToString(`is`: InputStream): String {
val reader = BufferedReader(InputStreamReader(`is`))
val sb = StringBuilder()
var line: String?
while (reader.readLine().also { line = it } != null) {
sb.append(line).append("\n")
}
reader.close()
return sb.toString()
}
fun getStringFromLrc(file: File?): String {
try {
val reader = BufferedReader(FileReader(file))
val sb = StringBuilder()
var line: String?
while (reader.readLine().also { line = it } != null) {
sb.append(line).append("\n")
}
reader.close()
return sb.toString()
} catch (e: Exception) {
Log.i("Error", "Error Occurred")
}
return ""
}
fun getSyncedLyricsFile(song: Song): File? {
return when {
isLrcOriginalFileExist(song.data) -> {
getLocalLyricOriginalFile(song.data)
}
isLrcFileExist(song.title, song.artistName) -> {
getLocalLyricFile(song.title, song.artistName)
}
else -> {
null
}
}
}
fun getEmbeddedSyncedLyrics(data: String): String? {
val embeddedLyrics = try{
AudioFileIO.read(File(data)).tagOrCreateDefault.getFirst(FieldKey.LYRICS)
} catch(e: Exception){
return null
}
return if (AbsSynchronizedLyrics.isSynchronized(embeddedLyrics)) {
embeddedLyrics
} else {
null
}
}
}

View file

@ -561,6 +561,8 @@ object PreferenceUtil {
} }
set(value) = sharedPreferences.edit { set(value) = sharedPreferences.edit {
putInt(NOW_PLAYING_SCREEN_ID, value.id) putInt(NOW_PLAYING_SCREEN_ID, value.id)
// Also set a cover theme for that now playing
value.defaultCoverTheme?.let { coverTheme -> albumCoverStyle = coverTheme }
} }
val albumCoverTransform: ViewPager.PageTransformer val albumCoverTransform: ViewPager.PageTransformer
@ -655,4 +657,7 @@ object PreferenceUtil {
val materialYou val materialYou
get() = sharedPreferences.getBoolean(MATERIAL_YOU, VersionUtils.hasS()) get() = sharedPreferences.getBoolean(MATERIAL_YOU, VersionUtils.hasS())
val isSnowFalling
get() = sharedPreferences.getBoolean(SNOWFALL, false)
} }

View file

@ -14,7 +14,6 @@
package code.name.monkey.retromusic.util package code.name.monkey.retromusic.util
import android.content.ContentValues
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
@ -32,14 +31,6 @@ class RingtoneManager(val context: Context) {
fun setRingtone(song: Song) { fun setRingtone(song: Song) {
val resolver = context.contentResolver val resolver = context.contentResolver
val uri = getSongFileUri(song.id) val uri = getSongFileUri(song.id)
try {
val values = ContentValues(2)
values.put(MediaStore.Audio.AudioColumns.IS_RINGTONE, "1")
values.put(MediaStore.Audio.AudioColumns.IS_ALARM, "1")
resolver.update(uri, values, null, null)
} catch (ignored: UnsupportedOperationException) {
return
}
try { try {
val cursor = resolver.query( val cursor = resolver.query(

View file

@ -26,7 +26,6 @@ import androidx.core.graphics.BlendModeCompat.SRC_IN
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.MaterialValueHelper import code.name.monkey.appthemehelper.util.MaterialValueHelper
import com.google.android.material.progressindicator.CircularProgressIndicator
object ViewUtil { object ViewUtil {

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