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'
android {
compileSdkVersion 31
compileSdk 31
defaultConfig {
minSdkVersion 21
targetSdkVersion 31
minSdk 21
targetSdk 31
renderscriptTargetApi 29//must match target sdk and build tools
vectorDrawables.useSupportLibrary = true
applicationId "code.name.monkey.retromusic"
versionCode 10545
versionName '5.4.2 ' + "_" + getDate()
versionCode 10551
versionName '5.5.0'
buildConfigField("String", "GOOGLE_PLAY_LICENSING_KEY", "\"${getProperty(getProperties('../public.properties'), 'GOOGLE_PLAY_LICENSE_KEY')}\"")
}
@ -31,9 +30,10 @@ android {
}
buildTypes {
release {
//debuggable true
versionNameSuffix "_" + getDate()
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
debug {
@ -95,22 +95,21 @@ dependencies {
implementation 'androidx.annotation:annotation:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
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.palette:palette-ktx:1.0.0'
//Cast Dependencies
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
implementation "org.nanohttpd:nanohttpd:2.3.1"
def nav_version = '2.4.0-beta02'
implementation "androidx.navigation:navigation-runtime-ktx:$nav_version"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
implementation "androidx.navigation:navigation-runtime-ktx:$navigation_version"
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
implementation "androidx.navigation:navigation-ui-ktx:$navigation_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-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"
@ -121,7 +120,7 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
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'
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
@ -138,7 +137,7 @@ dependencies {
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-android:$kotlin_coroutines_version"
@ -152,15 +151,19 @@ dependencies {
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 '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.r0adkll:slidableactivity:2.1.0'
implementation 'com.heinrichreimersoftware:material-intro:2.0.0'
implementation 'com.github.dhaval2404:imagepicker:2.1'
implementation 'me.zhanghai.android.fastscroll:library:1.1.7'
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'
}
apply from: '../spotless.gradle'
}

View file

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

View file

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

View file

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

View file

@ -22,11 +22,12 @@
<application
android:name=".App"
android:allowBackup="true"
android:allowBackup="@bool/allowBackup"
android:configChanges="locale|layoutDirection"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:restoreAnyVersion="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.RetroMusic.FollowSystem"
@ -122,15 +123,38 @@
<activity android:name=".activities.LockScreenActivity" />
<activity
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>
<action android:name="android.intent.action.VIEW" />
<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="content" />
<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" />
</intent-filter>
</activity>
@ -272,10 +296,9 @@
<service
android:name=".service.MusicService"
android:enabled="true"
android:exported="true"
android:exported="false"
android:foregroundServiceType="mediaPlayback"
android:label="@string/app_name"
tools:ignore="ExportedService">
android:label="@string/app_name">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
@ -298,4 +321,12 @@
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="code.name.monkey.retromusic.cast.CastOptionsProvider" />
</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>

View file

@ -24,40 +24,54 @@
padding-top: 8px;
}
</style>
</head>
<body>
<p><b><a href="https://github.com/kabouzeid/Phonograph" title="Phonograph"> Phonograph</a></b> by
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"
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/square/retrofit" title="Retrofit"> Retrofit</a></b> by Square team
</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">
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"
title="Android In-App Billing v3 Library"> Android In-App Billing v3 Library</a></b> by
Henning Dodenhof</p>
<p><b><a href="https://github.com/h6ah4i/android-advancedrecyclerview"
title="Advanced RecyclerView"> Advanced RecyclerView</a></b> by Haruki Hasegawa</p>
<p><b><a href="https://github.com/ksoichiro/Android-ObservableScrollView"
title="Android-ObservableScrollView"> Android-ObservableScrollView</a></b> by Soichiro
Kashima</p>
<p><b><a href="https://github.com/Ereza/CustomActivityOnCrash"
title="Custom Activity on Crash">Custom Activity on Crash</a></b> by Eduard Ereza Martínez
</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://www.techjuice.pk" title="City wallpaper"> Material Design City Wallpaper</a></b>
</p>
</body>
</html>

View file

@ -63,7 +63,75 @@
<body>
<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>
<h3>What's New</h3>
<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 WHITELIST_MUSIC = "whitelist_music"
const val MATERIAL_YOU = "material_you"
const val SNOWFALL = "snowfall"

View file

@ -15,16 +15,19 @@
package code.name.monkey.retromusic.activities
import android.animation.ObjectAnimator
import android.content.Intent
import android.graphics.Color
import android.graphics.PorterDuff
import android.os.Bundle
import android.view.animation.LinearInterpolator
import android.widget.SeekBar
import androidx.lifecycle.lifecycleScope
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.databinding.ActivityDriveModeBinding
import code.name.monkey.retromusic.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.glide.BlurTransformation
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.PlayPauseButtonOnClickHandler
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.util.MusicUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
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 lastDisabledPlaybackControlsColor: Int = Color.GRAY
private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper
private val repository: RealRepository by inject()
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
binding = ActivityDriveModeBinding.inflate(layoutInflater)
setContentView(binding.root)
@ -67,6 +72,7 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
binding.close.setOnClickListener {
onBackPressed()
}
binding.repeatButton.drawAboveSystemBars()
}
private fun setUpMusicControllers() {
@ -80,19 +86,32 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
private fun setupFavouriteToggle() {
binding.songFavourite.setOnClickListener {
MusicUtil.toggleFavorite(
this@DriveModeActivity,
MusicPlayerRemote.currentSong
)
toggleFavorite(MusicPlayerRemote.currentSong)
}
}
private fun toggleFavourite() {
CoroutineScope(Dispatchers.IO).launch {
val isFavourite =
MusicUtil.isFavorite(this@DriveModeActivity, MusicPlayerRemote.currentSong)
private fun toggleFavorite(song: Song) {
lifecycleScope.launch(Dispatchers.IO) {
val playlist = repository.favoritePlaylist()
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) {
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()
updateRepeatState()
updateShuffleState()
toggleFavourite()
updateFavorite()
}
private fun updatePlayPauseDrawableState() {
@ -213,7 +232,12 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
override fun onPlayingMetaChanged() {
super.onPlayingMetaChanged()
updateSong()
toggleFavourite()
updateFavorite()
}
override fun onFavoriteStateChanged() {
super.onFavoriteStateChanged()
updateFavorite()
}
private fun updateSong() {

View file

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

View file

@ -18,9 +18,9 @@ import android.app.KeyguardManager
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.WindowManager
import androidx.core.view.ViewCompat
import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.databinding.ActivityLockScreenBinding
@ -41,13 +41,11 @@ class LockScreenActivity : AbsMusicServiceActivity() {
private var fragment: LockScreenControlsFragment? = null
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
lockScreenInit()
binding = ActivityLockScreenBinding.inflate(layoutInflater)
setContentView(binding.root)
hideStatusBar()
setStatusBarColorAuto()
setTaskDescriptionColorAuto()
val config = SlidrConfig.Builder().listener(object : SlidrListener {
@ -61,7 +59,7 @@ class LockScreenActivity : AbsMusicServiceActivity() {
}
override fun onSlideClosed(): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (VersionUtils.hasOreo()) {
val keyguardManager =
getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
keyguardManager.requestDismissKeyguard(this@LockScreenActivity, null)
@ -75,7 +73,7 @@ class LockScreenActivity : AbsMusicServiceActivity() {
fragment = whichFragment<LockScreenControlsFragment>(R.id.playback_controls_fragment)
findViewById<View>(R.id.slide).apply {
binding.slide.apply {
translationY = 100f
alpha = 0f
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.databinding.SlidingMusicPanelLayoutBinding
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.SearchQueryHelper.getSongs
import code.name.monkey.retromusic.interfaces.IScrollHelper
import code.name.monkey.retromusic.model.CategoryInfo
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.repository.PlaylistSongsLoader
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.AppRater
import code.name.monkey.retromusic.util.NavigationUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch
@ -52,7 +52,6 @@ class MainActivity : AbsCastActivity(), OnSharedPreferenceChangeListener {
}
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setTaskDescriptionColorAuto()
hideStatusBar()
@ -63,6 +62,9 @@ class MainActivity : AbsCastActivity(), OnSharedPreferenceChangeListener {
if (!hasPermissions()) {
findNavController(R.id.fragment_container).navigate(R.id.permissionFragment)
}
if (BuildConfig.VERSION_CODE > PreferenceUtil.lastVersion){
NavigationUtil.gotoWhatNews(this)
}
}
private fun setupNavigationController() {
@ -90,21 +92,17 @@ class MainActivity : AbsCastActivity(), OnSharedPreferenceChangeListener {
bottomNavigationView.setupWithNavController(navController)
// Scroll Fragment to top
bottomNavigationView.setOnItemReselectedListener {
currentFragment(R.id.fragment_container)
.also {
if (it is AbsRecyclerViewFragment<*, *>) {
it.scrollToTop()
}
if (it is HomeFragment) {
it.scrollToTop()
}
currentFragment(R.id.fragment_container).apply {
if (this is IScrollHelper) {
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, _ ->
when (destination.id) {
if (destination.id == navGraph.startDestinationId) {
currentFragment(R.id.fragment_container)?.enterTransition = null
}
when (destination.id) {
R.id.action_home, R.id.action_song, R.id.action_album, R.id.action_artist, R.id.action_folder, R.id.action_playlist, R.id.action_genre -> {
// Save the last tab
if (PreferenceUtil.rememberLastTab) {
@ -114,10 +112,12 @@ class MainActivity : AbsCastActivity(), OnSharedPreferenceChangeListener {
setBottomNavVisibility(visible = true, animate = true)
}
R.id.playing_queue_fragment -> {
setBottomNavVisibility(visible = false)
hideBottomSheet(true)
setBottomNavVisibility(visible = false, 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.Bundle
import android.provider.Settings
import android.view.View
import androidx.annotation.RequiresApi
import androidx.core.text.HtmlCompat
import androidx.core.view.isVisible
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
@ -84,12 +84,12 @@ class PermissionActivity : AbsMusicServiceActivity() {
@RequiresApi(Build.VERSION_CODES.M)
override fun onResume() {
if (hasStoragePermission()) {
binding.storagePermission.checkImage.visibility = View.VISIBLE
binding.storagePermission.checkImage.isVisible = true
binding.storagePermission.checkImage.imageTintList =
ColorStateList.valueOf(ThemeStore.accentColor(this))
}
if (hasAudioPermission()) {
binding.audioPermission.checkImage.visibility = View.VISIBLE
binding.audioPermission.checkImage.isVisible = true
binding.audioPermission.checkImage.imageTintList =
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.activities.base.AbsBaseActivity
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.setStatusBarColor
import com.anjlab.android.iab.v3.BillingProcessor
@ -40,7 +39,6 @@ class PurchaseActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler {
private lateinit var billingProcessor: BillingProcessor
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
binding = ActivityProVersionBinding.inflate(layoutInflater)
setContentView(binding.root)

View file

@ -32,15 +32,18 @@ import com.afollestad.materialdialogs.color.ColorCallback
class SettingsActivity : AbsThemeActivity(), ColorCallback, OnThemeChangedListener {
private lateinit var binding: ActivitySettingsBinding
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
val mSavedInstanceState = extra<Bundle>(TAG).value ?: savedInstanceState
super.onCreate(mSavedInstanceState)
setLightStatusBarAuto(surfaceColor())
binding = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(binding.root)
setupToolbar()
}
override fun onResume() {
super.onResume()
setNavigationBarColorPreOreo(surfaceColor())
}
private fun setupToolbar() {
applyToolbar(binding.toolbar)
val navController: NavController = findNavController(R.id.contentFrame)
@ -82,7 +85,6 @@ class SettingsActivity : AbsThemeActivity(), ColorCallback, OnThemeChangedListen
ThemeStore.editTheme(this).accentColor(color).commit()
if (VersionUtils.hasNougatMR())
DynamicShortcutManager(this).updateDynamicShortcuts()
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.retromusic.activities.base.AbsBaseActivity
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.setStatusBarColor
import code.name.monkey.retromusic.glide.GlideApp
@ -60,7 +58,6 @@ class ShareInstagramStory : AbsBaseActivity() {
}
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
binding = ActivityShareInstagramBinding.inflate(layoutInflater)
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.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.databinding.ActivityDonationBinding
import code.name.monkey.retromusic.extensions.setStatusBarColorAuto
import code.name.monkey.retromusic.extensions.setTaskDescriptionColorAuto
import code.name.monkey.retromusic.extensions.textColorPrimary
import code.name.monkey.retromusic.extensions.textColorSecondary
import code.name.monkey.retromusic.extensions.*
import com.anjlab.android.iab.v3.BillingProcessor
import com.anjlab.android.iab.v3.PurchaseInfo
import com.anjlab.android.iab.v3.SkuDetails
@ -86,7 +83,7 @@ class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingH
}
private fun setupToolbar() {
val toolbarColor = ATHUtil.resolveColor(this, R.attr.colorSurface)
val toolbarColor = surfaceColor()
binding.toolbar.setBackgroundColor(toolbarColor)
ToolbarContentTintHelper.colorBackButton(binding.toolbar)
setSupportActionBar(binding.toolbar)

View file

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

View file

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

View file

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

View file

@ -34,7 +34,6 @@ import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding
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.VersionUtils
import code.name.monkey.retromusic.R
@ -60,14 +59,13 @@ import java.io.File
import java.util.*
abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
abstract val editorImage: ImageView?
abstract val editorImage: ImageView
val repository by inject<Repository>()
lateinit var saveFab: MaterialButton
protected var id: Long = 0
private set
private var paletteColorPrimary: Int = 0
private var isInNoImageMode: Boolean = false
private var songPaths: List<String>? = null
private var savedSongPaths: List<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?
get() {
return try {
@ -239,7 +246,7 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
getString(R.string.web_search),
getString(R.string.remove_cover)
)
editorImage?.setOnClickListener { show }
editorImage.setOnClickListener { show }
}
private fun startImagePicker() {
@ -306,17 +313,6 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
return super.onOptionsItemSelected(item)
}
protected fun setNoImageMode() {
isInNoImageMode = true
setColors(
intent.getIntExtra(
EXTRA_PALETTE,
ATHUtil.resolveColor(this, R.attr.colorPrimary)
)
)
}
protected fun dataChanged() {
showFab()
}
@ -335,9 +331,9 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
protected fun setImageBitmap(bitmap: Bitmap?, bgColor: Int) {
if (bitmap == null) {
editorImage?.setImageResource(drawable.default_audio_art)
editorImage.setImageResource(drawable.default_audio_art)
} else {
editorImage?.setImageBitmap(bitmap)
editorImage.setImageBitmap(bitmap)
}
setColors(bgColor)
}

View file

@ -28,11 +28,12 @@ import android.transition.Slide
import android.view.LayoutInflater
import android.widget.ImageView
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.databinding.ActivityAlbumTagEditorBinding
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.glide.GlideApp
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
@ -64,41 +65,6 @@ class AlbumTagEditorActivity : AbsTagEditorActivity<ActivityAlbumTagEditorBindin
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 deleteAlbumArt: Boolean = false
@ -109,7 +75,6 @@ class AlbumTagEditorActivity : AbsTagEditorActivity<ActivityAlbumTagEditorBindin
}
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
window.sharedElementsUseOverlay = true
binding.imageContainer.transitionName = getString(R.string.transition_album_art)
@ -146,7 +111,7 @@ class AlbumTagEditorActivity : AbsTagEditorActivity<ActivityAlbumTagEditorBindin
bitmap,
getColor(
generatePalette(bitmap),
ATHUtil.resolveColor(this, R.attr.defaultFooterColor)
defaultFooterColor()
)
)
deleteAlbumArt = false
@ -167,12 +132,44 @@ class AlbumTagEditorActivity : AbsTagEditorActivity<ActivityAlbumTagEditorBindin
override fun deleteImage() {
setImageBitmap(
BitmapFactory.decodeResource(resources, R.drawable.default_audio_art),
ATHUtil.resolveColor(this, R.attr.defaultFooterColor)
defaultFooterColor()
)
deleteAlbumArt = true
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() {
val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java)
fieldKeyValueMap[FieldKey.ALBUM] = binding.albumText.text.toString()
@ -215,6 +212,16 @@ class AlbumTagEditorActivity : AbsTagEditorActivity<ActivityAlbumTagEditorBindin
override fun setColors(color: Int) {
super.setColors(color)
saveFab.backgroundTintList = ColorStateList.valueOf(color)
saveFab.backgroundTintList = ColorStateList.valueOf(color)
ColorStateList.valueOf(
MaterialValueHelper.getPrimaryTextColor(
this,
color.isColorLight
)
).also {
saveFab.iconTint = it
saveFab.setTextColor(it)
}
}

View file

@ -15,17 +15,36 @@
package code.name.monkey.retromusic.activities.tageditor
import android.annotation.SuppressLint
import android.app.Activity
import android.content.res.ColorStateList
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
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.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.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.util.ImageUtil
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 org.jaudiotagger.tag.FieldKey
import org.koin.android.ext.android.inject
@ -39,12 +58,14 @@ class SongTagEditorActivity : AbsTagEditorActivity<ActivitySongTagEditorBinding>
private val songRepository by inject<SongRepository>()
private var albumArtBitmap: Bitmap? = null
private var deleteAlbumArt: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setUpViews()
setNoImageMode()
setSupportActionBar(binding.toolbar)
binding.appBarLayout.statusBarForeground =
binding.appBarLayout?.statusBarForeground =
MaterialShapeDrawable.createWithElevationOverlay(this)
}
@ -59,6 +80,7 @@ class SongTagEditorActivity : AbsTagEditorActivity<ActivitySongTagEditorBinding>
binding.yearContainer.setTint(false)
binding.genreContainer.setTint(false)
binding.trackNumberContainer.setTint(false)
binding.discNumberContainer.setTint(false)
binding.lyricsContainer.setTint(false)
binding.songText.appHandleColor().addTextChangedListener(this)
@ -68,13 +90,9 @@ class SongTagEditorActivity : AbsTagEditorActivity<ActivitySongTagEditorBinding>
binding.genreText.appHandleColor().addTextChangedListener(this)
binding.yearText.appHandleColor().addTextChangedListener(this)
binding.trackNumberText.appHandleColor().addTextChangedListener(this)
binding.discNumberText.appHandleColor().addTextChangedListener(this)
binding.lyricsText.appHandleColor().addTextChangedListener(this)
binding.songComposerText.appHandleColor().addTextChangedListener(this)
binding.lyricsText.setOnTouchListener { view, _ ->
view.parent.requestDisallowInterceptTouchEvent(true)
return@setOnTouchListener false
}
}
private fun fillViewsWithFileTags() {
@ -85,16 +103,50 @@ class SongTagEditorActivity : AbsTagEditorActivity<ActivitySongTagEditorBinding>
binding.genreText.setText(genreName)
binding.yearText.setText(songYear)
binding.trackNumberText.setText(trackNumber)
binding.discNumberText.setText(discNumber)
binding.lyricsText.setText(lyrics)
binding.songComposerText.setText(composer)
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() {
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.YEAR] = binding.yearText.text.toString()
fieldKeyValueMap[FieldKey.TRACK] = binding.trackNumberText.text.toString()
fieldKeyValueMap[FieldKey.DISC_NO] = binding.discNumberText.text.toString()
fieldKeyValueMap[FieldKey.LYRICS] = binding.lyricsText.text.toString()
fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = binding.albumArtistText.text.toString()
fieldKeyValueMap[FieldKey.COMPOSER] = binding.songComposerText.text.toString()
writeValuesToFiles(fieldKeyValueMap, 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)
@ -115,6 +174,35 @@ class SongTagEditorActivity : AbsTagEditorActivity<ActivitySongTagEditorBinding>
override fun getSongUris(): List<Uri> = listOf(MusicUtil.getSongFileUri(id))
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) {
@ -131,6 +219,6 @@ class SongTagEditorActivity : AbsTagEditorActivity<ActivitySongTagEditorBinding>
val TAG: String = SongTagEditorActivity::class.java.simpleName
}
override val editorImage: ImageView?
get() = null
override val editorImage: ImageView
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.ReadOnlyFileException
import org.jaudiotagger.tag.TagException
import org.jaudiotagger.tag.images.AndroidArtwork
import org.jaudiotagger.tag.images.Artwork
import org.jaudiotagger.tag.images.ArtworkFactory
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
@ -58,11 +58,11 @@ class TagWriter {
try {
albumArtFile = createAlbumArtFile(context).canonicalFile
info.artworkInfo.artwork.compress(
Bitmap.CompressFormat.PNG,
0,
Bitmap.CompressFormat.JPEG,
100,
FileOutputStream(albumArtFile)
)
artwork = ArtworkFactory.createArtworkFromFile(albumArtFile)
artwork = AndroidArtwork.createArtworkFromFile(albumArtFile)
} catch (e: IOException) {
e.printStackTrace()
}
@ -131,11 +131,11 @@ class TagWriter {
try {
albumArtFile = createAlbumArtFile(context).canonicalFile
info.artworkInfo.artwork.compress(
Bitmap.CompressFormat.PNG,
0,
Bitmap.CompressFormat.JPEG,
100,
FileOutputStream(albumArtFile)
)
artwork = ArtworkFactory.createArtworkFromFile(albumArtFile)
artwork = AndroidArtwork.createArtworkFromFile(albumArtFile)
} catch (e: IOException) {
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.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.menu.SongMenuHelper
import code.name.monkey.retromusic.model.*
import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist
import code.name.monkey.retromusic.repository.PlaylistSongsLoader
import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.model.Genre
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil
import java.util.*

View file

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

View file

@ -4,9 +4,9 @@ import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.PopupMenu
import android.widget.TextView
import androidx.appcompat.widget.AppCompatImageView
import androidx.appcompat.widget.PopupMenu
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.retromusic.R

View file

@ -7,8 +7,8 @@ import android.view.MenuItem
import androidx.annotation.MenuRes
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.util.ATHUtil
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.ICabHolder
import code.name.monkey.retromusic.util.RetroColorUtil
@ -25,7 +25,7 @@ abstract class AbsMultiSelectAdapter<V : RecyclerView.ViewHolder?, I>(
private var menuRes: Int
override fun onCabCreated(cab: AttachedCab, menu: Menu): Boolean {
activity.window.statusBarColor =
RetroColorUtil.shiftBackgroundColor(ATHUtil.resolveColor(activity, R.attr.colorSurface))
RetroColorUtil.shiftBackgroundColor(activity.surfaceColor())
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.glide.GlideApp
import code.name.monkey.retromusic.glide.playlistPreview.PlaylistPreview
import code.name.monkey.retromusic.helper.SortOrder.PlaylistSortOrder
import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper
import code.name.monkey.retromusic.helper.menu.SongsMenuHelper
import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.interfaces.IPlaylistClickListener
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import me.zhanghai.android.fastscroll.PopupTextProvider
class PlaylistAdapter(
override val activity: FragmentActivity,
@ -52,7 +55,7 @@ class PlaylistAdapter(
activity,
ICabHolder,
R.menu.menu_playlists_selection
) {
), PopupTextProvider {
init {
setHasStableIds(true)
@ -84,6 +87,17 @@ class PlaylistAdapter(
return MusicUtil.getPlaylistInfoString(activity, playlist.songs.toSongs())
}
override fun getPopupText(position: Int): String {
val sectionName: String = when (PreferenceUtil.playlistSortOrder) {
PlaylistSortOrder.PLAYLIST_A_Z, PlaylistSortOrder.PLAYLIST_Z_A -> dataSet[position].playlistEntity.playlistName
PlaylistSortOrder.PLAYLIST_SONG_COUNT, PlaylistSortOrder.PLAYLIST_SONG_COUNT_DESC -> dataSet[position].songs.size.toString()
else -> {
return ""
}
}
return MusicUtil.getSectionName(sectionName)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val playlist = dataSet[position]
holder.itemView.isActivated = isChecked(playlist)

View file

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

View file

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

View file

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

View file

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

View file

@ -14,14 +14,16 @@
*/
package code.name.monkey.retromusic.extensions
import android.R
import android.app.Activity
import android.view.View
import android.view.ViewGroup
import androidx.annotation.DimenRes
import androidx.appcompat.app.AppCompatActivity
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import com.google.android.material.appbar.MaterialToolbar
fun AppCompatActivity.applyToolbar(toolbar: MaterialToolbar) {
//toolbar.setBackgroundColor(surfaceColor())
ToolbarContentTintHelper.colorBackButton(toolbar)
setSupportActionBar(toolbar)
}
@ -38,4 +40,6 @@ inline fun <reified T : Any> Activity.extraNotNull(key: String, default: T? = nu
fun Activity.dip(@DimenRes id: Int): Int {
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.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.isGone
import androidx.fragment.app.FragmentActivity
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.VersionUtils
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil
fun AppCompatActivity.toggleScreenOn() {
if (PreferenceUtil.isScreenOnEnabled) {
@ -43,12 +41,10 @@ fun AppCompatActivity.setImmersiveFullscreen() {
fun AppCompatActivity.exitFullscreen() {
WindowInsetsControllerCompat(window, window.decorView).apply {
systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
show(WindowInsetsCompat.Type.systemBars())
}
}
fun AppCompatActivity.hideStatusBar() {
hideStatusBar(PreferenceUtil.isFullScreenMode)
}
@ -56,13 +52,26 @@ fun AppCompatActivity.hideStatusBar() {
private fun AppCompatActivity.hideStatusBar(fullscreen: Boolean) {
val statusBar = window.decorView.rootView.findViewById<View>(R.id.status_bar)
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)
window.statusBarColor = Color.TRANSPARENT
if (VersionUtils.hasOreo()) {
if (VersionUtils.hasQ()) {
window.isNavigationBarContrastEnforced = false
}
window.navigationBarColor = Color.TRANSPARENT
window.statusBarColor = Color.TRANSPARENT
} else {
setNavigationBarColorPreOreo(surfaceColor())
if (VersionUtils.hasMarshmallow()) {
setStatusBarColor(Color.TRANSPARENT)
} else {
setStatusBarColor(surfaceColor())
}
}
}
fun FragmentActivity.setTaskDescriptionColor(color: Int) {
@ -100,9 +109,11 @@ fun AppCompatActivity.setLightStatusBarAuto(bgColor: Int) {
}
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)
}
}
setLightStatusBarAuto(ATHUtil.resolveColor(this, R.attr.colorSurface))
setLightStatusBarAuto(surfaceColor())
}
fun AppCompatActivity.setStatusBarColorAuto() {
// 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))
setLightStatusBarAuto(ATHUtil.resolveColor(this, R.attr.colorSurface))
setStatusBarColor(surfaceColor())
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 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 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 Context.defaultFooterColor() = resolveColor(R.attr.defaultFooterColor)
fun Fragment.defaultFooterColor() = resolveColor(R.attr.defaultFooterColor)
fun Context.resolveColor(@AttrRes attr: Int, fallBackColor: Int = 0) =
ATHUtil.resolveColor(this, attr, fallBackColor)
@ -120,6 +128,15 @@ fun MaterialButton.accentOutlineColor() {
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) {
thumbTintList = 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
get() = ColorUtil.isColorLight(this)

View file

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

View file

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

View file

@ -2,7 +2,6 @@ package code.name.monkey.retromusic.extensions
import android.net.Uri
import android.webkit.MimeTypeMap
import androidx.fragment.app.Fragment
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.RetroUtil
import org.jaudiotagger.audio.AudioFileIO

View file

@ -1,9 +1,12 @@
package code.name.monkey.retromusic.extensions
import androidx.core.view.WindowInsetsCompat
import code.name.monkey.retromusic.util.RetroUtil
import code.name.monkey.retromusic.util.PreferenceUtil
fun WindowInsetsCompat?.safeGetBottomInsets(): Int {
// Get Navbar heights if insets are null
return (this?.getInsets(WindowInsetsCompat.Type.systemBars())?.bottom ?: RetroUtil.getNavigationBarHeight())
return if (PreferenceUtil.isFullScreenMode) {
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.PreferenceUtil
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
@ -233,6 +234,7 @@ class LibraryViewModel(
suspend fun artistById(id: Long) = repository.artistById(id)
suspend fun favoritePlaylist() = repository.favoritePlaylist()
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 removeSongFromPlaylist(songEntity: SongEntity) =
repository.removeSongFromPlaylist(songEntity)
@ -356,11 +358,21 @@ class LibraryViewModel(
it.toSongEntity(playListId = playlist.playListId)
})
}
Toast.makeText(
App.getContext(),
"Adding songs to $playlistName",
Toast.LENGTH_SHORT
).show()
withContext(Main) {
Toast.makeText(
App.getContext(),
"Playlist already exists",
Toast.LENGTH_SHORT
).show()
if (songs.isNotEmpty()) {
Toast.makeText(
App.getContext(),
"Adding songs to $playlistName",
Toast.LENGTH_SHORT
).show()
}
}
}
}
}

View file

@ -22,24 +22,26 @@ enum class NowPlayingScreen constructor(
@param:StringRes @field:StringRes
val titleRes: 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),
Blur(R.string.blur, R.drawable.np_blur, 4),
BlurCard(R.string.blur_card, R.drawable.np_blur_card, 9),
Card(R.string.card, R.drawable.np_card, 6),
Circle(R.string.circle, R.drawable.np_minimalistic_circle, 15),
Classic(R.string.classic, R.drawable.np_classic, 16),
Color(R.string.color, R.drawable.np_color, 5),
Fit(R.string.fit, R.drawable.np_fit, 12),
Flat(R.string.flat, R.drawable.np_flat, 1),
Full(R.string.full, R.drawable.np_full, 2),
Gradient(R.string.gradient, R.drawable.np_gradient, 17),
Material(R.string.material, R.drawable.np_material, 11),
Normal(R.string.normal, R.drawable.np_normal, 0),
Peak(R.string.peak, R.drawable.np_peak, 14),
Plain(R.string.plain, R.drawable.np_plain, 3),
Simple(R.string.simple, R.drawable.np_simple, 8),
Tiny(R.string.tiny, R.drawable.np_tiny, 7),
Adaptive(R.string.adaptive, R.drawable.np_adaptive, 10, AlbumCoverStyle.FullCard),
Blur(R.string.blur, R.drawable.np_blur, 4, AlbumCoverStyle.Normal),
BlurCard(R.string.blur_card, R.drawable.np_blur_card, 9, AlbumCoverStyle.Card),
Card(R.string.card, R.drawable.np_card, 6, AlbumCoverStyle.Full),
Circle(R.string.circle, R.drawable.np_minimalistic_circle, 15, null),
Classic(R.string.classic, R.drawable.np_classic, 16, AlbumCoverStyle.Full),
Color(R.string.color, R.drawable.np_color, 5, AlbumCoverStyle.Normal),
Fit(R.string.fit, R.drawable.np_fit, 12, AlbumCoverStyle.Full),
Flat(R.string.flat, R.drawable.np_flat, 1, AlbumCoverStyle.Flat),
Full(R.string.full, R.drawable.np_full, 2, AlbumCoverStyle.Full),
Gradient(R.string.gradient, R.drawable.np_gradient, 17, AlbumCoverStyle.Full),
Material(R.string.material, R.drawable.np_material, 11, AlbumCoverStyle.Normal),
Normal(R.string.normal, R.drawable.np_normal, 0, AlbumCoverStyle.Normal),
Peak(R.string.peak, R.drawable.np_peak, 14, AlbumCoverStyle.Normal),
Plain(R.string.plain, R.drawable.np_plain, 3, AlbumCoverStyle.Normal),
Simple(R.string.simple, R.drawable.np_simple, 8, AlbumCoverStyle.Normal),
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.R
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.fragments.GridStyle
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?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentArtistDetailsBinding.bind(view)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setHasOptionsMenu(true)
mainActivity.addMusicServiceEventListener(detailsViewModel)
mainActivity.setSupportActionBar(binding.toolbar)

View file

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

View file

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

View file

@ -1,12 +1,89 @@
package code.name.monkey.retromusic.fragments.backup
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import androidx.activity.viewModels
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() {
lateinit var binding: ActivityRestoreBinding
private val backupViewModel: BackupViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
updateTheme()
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.view.View
import androidx.annotation.LayoutRes
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.extensions.setLightStatusBarAuto
import code.name.monkey.retromusic.extensions.setTaskDescriptionColorAuto
import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.fragments.LibraryViewModel
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
@ -52,7 +52,7 @@ abstract class AbsMainActivityFragment(@LayoutRes layout: Int) : AbsMusicService
}
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
if (VersionUtils.hasMarshmallow()) {
setStatusBarColor(view, colorPrimary)

View file

@ -25,7 +25,6 @@ import android.media.MediaMetadataRetriever
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
import android.text.TextUtils
import android.view.GestureDetector
import android.view.MenuItem
import android.view.MotionEvent
@ -33,6 +32,7 @@ import android.view.View
import android.widget.RelativeLayout
import android.widget.Toast
import androidx.annotation.LayoutRes
import androidx.appcompat.graphics.drawable.DrawableWrapper
import androidx.appcompat.widget.Toolbar
import androidx.core.os.bundleOf
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.SongTagEditorActivity
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.dialogs.*
import code.name.monkey.retromusic.extensions.currentFragment
import code.name.monkey.retromusic.extensions.hide
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.player.PlayerAlbumCoverFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.IPaletteColorHolder
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.service.MusicService
import code.name.monkey.retromusic.util.*
@ -67,7 +66,6 @@ import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.android.ext.android.get
import java.io.FileNotFoundException
import kotlin.math.abs
abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragment(layout),
@ -81,8 +79,12 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
val song = MusicPlayerRemote.currentSong
when (item.itemId) {
R.id.action_toggle_lyrics -> {
PreferenceUtil.showLyrics = !PreferenceUtil.showLyrics
showLyricsIcon(item)
PreferenceUtil.showLyrics = !item.isChecked
item.isChecked = !item.isChecked
return true
}
R.id.action_go_to_lyrics -> {
goToLyrics(requireActivity())
return true
}
R.id.action_toggle_favorite -> {
@ -191,18 +193,6 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
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 onShow()
@ -219,7 +209,6 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
override fun onPlayingMetaChanged() {
updateIsFavorite()
updateLyrics()
}
override fun onFavoriteStateChanged() {
@ -231,7 +220,7 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
val playlist: PlaylistEntity = libraryViewModel.favoritePlaylist()
if (playlist != null) {
val songEntity = song.toSongEntity(playlist.playListId)
val isFavorite = libraryViewModel.isFavoriteSong(songEntity).isNotEmpty()
val isFavorite = libraryViewModel.isSongFavorite(song.id)
if (isFavorite) {
libraryViewModel.removeSongFromPlaylist(songEntity)
} else {
@ -245,32 +234,28 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
fun updateIsFavorite(animate: Boolean = false) {
lifecycleScope.launch(IO) {
val playlist: PlaylistEntity = libraryViewModel.favoritePlaylist()
if (playlist != null) {
val song: SongEntity =
MusicPlayerRemote.currentSong.toSongEntity(playlist.playListId)
val isFavorite: Boolean = libraryViewModel.isFavoriteSong(song).isNotEmpty()
withContext(Main) {
val icon = if (animate) {
if (isFavorite) R.drawable.avd_favorite else R.drawable.avd_unfavorite
} else {
if (isFavorite) R.drawable.ic_favorite else R.drawable.ic_favorite_border
}
val drawable: Drawable = RetroUtil.getTintedVectorDrawable(
requireContext(),
icon,
toolbarIconColor()
)
if (playerToolbar() != null) {
playerToolbar()?.menu?.findItem(R.id.action_toggle_favorite)?.apply {
setIcon(drawable)
title =
if (isFavorite) getString(R.string.action_remove_from_favorites)
else getString(R.string.action_add_to_favorites)
getIcon().also {
if (it is AnimatedVectorDrawable) {
it.start()
}
val isFavorite: Boolean =
libraryViewModel.isSongFavorite(MusicPlayerRemote.currentSong.id)
withContext(Main) {
val icon = if (animate) {
if (isFavorite) R.drawable.avd_favorite else R.drawable.avd_unfavorite
} else {
if (isFavorite) R.drawable.ic_favorite else R.drawable.ic_favorite_border
}
val drawable: Drawable = RetroUtil.getTintedVectorDrawable(
requireContext(),
icon,
toolbarIconColor()
)
if (playerToolbar() != null) {
playerToolbar()?.menu?.findItem(R.id.action_toggle_favorite)?.apply {
setIcon(drawable)
title =
if (isFavorite) getString(R.string.action_remove_from_favorites)
else getString(R.string.action_add_to_favorites)
getIcon().also {
if (it is AnimatedVectorDrawable) {
it.start()
}
}
}
@ -279,32 +264,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?) {
super.onViewCreated(view, savedInstanceState)
if (PreferenceUtil.isFullScreenMode &&
@ -320,8 +279,19 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
}
@SuppressLint("ClickableViewAccessibility")
override fun onStart() {
super.onStart()
override fun onResume() {
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(
SwipeDetector(
requireContext(),
@ -329,7 +299,6 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
requireView()
)
)
playerToolbar()?.menu?.findItem(R.id.action_toggle_lyrics)?.let { showLyricsIcon(it) }
}
class SwipeDetector(val context: Context, val viewPager: ViewPager?, val view: View) :
@ -443,4 +412,15 @@ fun goToLyrics(activity: Activity) {
null
)
}
}
}
/** 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.drawNextToNavbar
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 com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.transition.MaterialFadeThrough
@ -42,7 +44,7 @@ import me.zhanghai.android.fastscroll.FastScroller
import me.zhanghai.android.fastscroll.FastScrollerBuilder
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 val binding get() = _binding!!
@ -56,15 +58,15 @@ abstract class AbsRecyclerViewFragment<A : RecyclerView.Adapter<*>, LM : Recycle
_binding = FragmentMainRecyclerBinding.bind(view)
postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() }
enterTransition = MaterialFadeThrough().apply {
addTarget(binding.recyclerView)
}
enterTransition = MaterialFadeThrough().addTarget(binding.recyclerView)
reenterTransition = MaterialFadeThrough().addTarget(binding.recyclerView)
mainActivity.setSupportActionBar(binding.toolbar)
mainActivity.supportActionBar?.title = null
initLayoutManager()
initAdapter()
setUpRecyclerView()
setupToolbar()
binding.shuffleButton.fitsSystemWindows = PreferenceUtil.isFullScreenMode
// Add listeners when shuffle is visible
if (isShuffleVisible) {
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
@ -200,7 +202,7 @@ abstract class AbsRecyclerViewFragment<A : RecyclerView.Adapter<*>, LM : Recycle
val container get() = binding.root
fun scrollToTop() {
override fun scrollToTop() {
recyclerView.scrollToPosition(0)
binding.appBarLayout.setExpanded(true, true)
}

View file

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

View file

@ -23,6 +23,7 @@ import android.view.View
import androidx.activity.addCallback
import androidx.core.os.bundleOf
import androidx.core.text.HtmlCompat
import androidx.core.view.doOnLayout
import androidx.core.view.doOnPreDraw
import androidx.navigation.fragment.FragmentNavigatorExtras
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.extensions.accentColor
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.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.interfaces.IScrollHelper
import code.name.monkey.retromusic.util.PreferenceUtil
import com.google.android.gms.cast.framework.CastButtonFactory
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
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 val binding get() = _binding!!
@ -60,9 +64,8 @@ class HomeFragment :
setupListeners()
binding.titleWelcome.text = String.format("%s", PreferenceUtil.userName)
enterTransition = MaterialFadeThrough().apply {
addTarget(binding.contentContainer)
}
enterTransition = MaterialFadeThrough().addTarget(binding.contentContainer)
reenterTransition = MaterialFadeThrough().addTarget(binding.contentContainer)
val homeAdapter = HomeAdapter(mainActivity)
binding.recyclerView.apply {
@ -75,6 +78,7 @@ class HomeFragment :
loadProfile()
setupTitle()
colorButtons()
postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() }
binding.appBarLayout.statusBarForeground =
@ -84,6 +88,21 @@ class HomeFragment :
remove()
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() {
@ -172,6 +191,13 @@ class HomeFragment :
.into(binding.userImage)
}
fun colorButtons() {
binding.history.elevatedAccentColor()
binding.lastAdded.elevatedAccentColor()
binding.topPlayed.elevatedAccentColor()
binding.actionShuffle.elevatedAccentColor()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.menu_main, menu)
@ -189,7 +215,7 @@ class HomeFragment :
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.action_cast)
}
fun scrollToTop() {
override fun scrollToTop() {
binding.container.scrollTo(0, 0)
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)
}
}
binding.appBarLayout.statusBarForeground =
MaterialShapeDrawable.createWithElevationOverlay(requireContext())
postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() }
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
mainActivity.setSupportActionBar(binding.toolbar)
binding.progressIndicator.hide()
when (args.type) {
@ -92,6 +84,10 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
binding.recyclerView.updatePadding(bottom = height.toInt())
}
})
binding.appBarLayout.statusBarForeground =
MaterialShapeDrawable.createWithElevationOverlay(requireContext())
postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() }
}
private fun lastAddedSongs() {
@ -104,6 +100,7 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
binding.recyclerView.apply {
adapter = songAdapter
layoutManager = linearLayoutManager()
scheduleLayoutAnimation()
}
libraryViewModel.recentSongs().observe(viewLifecycleOwner, { songs ->
songAdapter.swapDataSet(songs)

View file

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

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

View file

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

View file

@ -14,28 +14,30 @@
*/
package code.name.monkey.retromusic.fragments.player
import android.annotation.SuppressLint
import android.content.SharedPreferences
import android.graphics.Color
import android.os.Bundle
import android.text.TextUtils
import android.view.View
import android.widget.FrameLayout
import android.widget.TextView
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import androidx.viewpager.widget.ViewPager
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.SHOW_LYRICS
import code.name.monkey.retromusic.adapter.album.AlbumCoverPagerAdapter
import code.name.monkey.retromusic.adapter.album.AlbumCoverPagerAdapter.AlbumCoverFragment
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.base.AbsMusicServiceFragment
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.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.transform.CarousalPagerTransformer
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.launch
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),
ViewPager.OnPageChangeListener, MusicProgressViewUpdateHelper.Callback,
@ -70,9 +67,7 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
}
private var progressViewUpdateHelper: MusicProgressViewUpdateHelper? = null
private val lyricsLayout: FrameLayout get() = binding.playerLyrics
private val lyricsLine1: TextView get() = binding.playerLyricsLine1
private val lyricsLine2: TextView get() = binding.playerLyricsLine2
private val lrcView: CoverLrcView get() = binding.lyricsView
var lyrics: Lyrics? = null
@ -82,102 +77,33 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
}
private fun updateLyrics() {
lyrics = null
binding.lyricsView.setLabel(context?.getString(R.string.no_lyrics_found))
val song = MusicPlayerRemote.currentSong
lifecycleScope.launch(Dispatchers.IO) {
val song = MusicPlayerRemote.currentSong
val lyrics = try {
var lrcFile: File? = null
if (LyricUtil.isLrcOriginalFileExist(song.data)) {
lrcFile = LyricUtil.getLocalLyricOriginalFile(song.data)
} else if (LyricUtil.isLrcFileExist(song.title, song.artistName)) {
lrcFile = LyricUtil.getLocalLyricFile(song.title, song.artistName)
val lrcFile = LyricUtil.getSyncedLyricsFile(song)
if (lrcFile != null) {
withContext(Dispatchers.Main) {
binding.lyricsView.loadLrc(lrcFile)
}
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 {
val embeddedLyrics = LyricUtil.getEmbeddedSyncedLyrics(song.data)
withContext(Dispatchers.Main) {
if (embeddedLyrics != null) {
binding.lyricsView.loadLrc(embeddedLyrics)
} else {
null
binding.lyricsView.reset()
}
}
} catch (err: FileNotFoundException) {
null
} catch (e: CannotReadException){
null
}
withContext(Dispatchers.Main) {
this@PlayerAlbumCoverFragment.lyrics = lyrics
}
}
}
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.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
}
binding.lyricsView.updateTime(progress.toLong())
}
@SuppressLint("ClickableViewAccessibility")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentPlayerAlbumCoverBinding.bind(view)
@ -208,27 +134,34 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
)
}
progressViewUpdateHelper = MusicProgressViewUpdateHelper(this, 500, 1000)
// Don't show lyrics container for below conditions
if (!(nps == Circle || nps == Peak || nps == Tiny || !PreferenceUtil.showLyrics)) {
progressViewUpdateHelper?.start()
}
// Go to lyrics activity when clicked lyrics
binding.playerLyricsLine2.setOnClickListener {
goToLyrics(requireActivity())
maybeInitLyrics()
lrcView.apply {
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
}
}
}
}
}
override fun onResume() {
super.onResume()
val nps = PreferenceUtil.nowPlayingScreen
// 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()
}
maybeInitLyrics()
PreferenceManager.getDefaultSharedPreferences(requireContext())
.registerOnSharedPreferenceChangeListener(this)
}
@ -259,25 +192,51 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
if (key == SHOW_LYRICS) {
if (sharedPreferences.getBoolean(key, false)) {
progressViewUpdateHelper?.start()
lyricsLayout.animate().alpha(1f).duration =
AbsPlayerFragment.VISIBILITY_ANIM_DURATION
binding.playerLyrics.isVisible = true
maybeInitLyrics()
} else {
showLyrics(false)
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()
}
}
private fun updatePlayingQueue() {
binding.viewPager.apply {
adapter = AlbumCoverPagerAdapter(childFragmentManager, MusicPlayerRemote.playingQueue)
@ -309,6 +268,19 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
private fun notifyColorChange(color: MediaNotificationProcessor) {
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) {
@ -325,4 +297,7 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
companion object {
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.view.View
import androidx.appcompat.widget.Toolbar
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentAdaptivePlayerBinding
import code.name.monkey.retromusic.extensions.drawAboveSystemBars
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.extensions.*
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote
@ -47,7 +43,7 @@ class AdaptiveFragment : AbsPlayerFragment(R.layout.fragment_adaptive_player) {
_binding = FragmentAdaptivePlayerBinding.bind(view)
setUpSubFragments()
setUpPlayerToolbar()
binding.root.drawAboveSystemBars()
binding.playbackControlsFragment.drawAboveSystemBars()
}
private fun setUpSubFragments() {
@ -108,7 +104,7 @@ class AdaptiveFragment : AbsPlayerFragment(R.layout.fragment_adaptive_player) {
libraryViewModel.updateColor(color.primaryTextColor)
ToolbarContentTintHelper.colorizeToolbar(
binding.playerToolbar,
ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal),
colorControlNormal(),
requireActivity()
)
}
@ -130,7 +126,7 @@ class AdaptiveFragment : AbsPlayerFragment(R.layout.fragment_adaptive_player) {
}
override fun toolbarIconColor(): Int {
return ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal)
return colorControlNormal()
}
override val paletteColor: Int

View file

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

View file

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

View file

@ -18,19 +18,19 @@ import android.animation.ObjectAnimator
import android.content.Context
import android.graphics.Color
import android.graphics.PorterDuff
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.media.AudioManager
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.Animation
import android.view.animation.LinearInterpolator
import android.widget.SeekBar
import androidx.appcompat.widget.Toolbar
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.TintHelper
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.appthemehelper.util.*
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentCirclePlayerBinding
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.goToAlbum
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.MusicProgressViewUpdateHelper
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.ViewUtil
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.OnAudioVolumeChangedListener
import me.tankery.lib.circularseekbar.CircularSeekBar
/**
* 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,
OnAudioVolumeChangedListener,
OnSeekArcChangeListener {
CircularSeekBar.OnCircularSeekBarChangeListener {
private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper
private var audioVolumeObserver: AudioVolumeObserver? = null
@ -69,6 +69,9 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
private var _binding: FragmentCirclePlayerBinding? = null
private val binding get() = _binding!!
private var rotateAnimator: ObjectAnimator? = null
private var lastRequest: GlideRequest<Drawable>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
progressViewUpdateHelper = MusicProgressViewUpdateHelper(this)
@ -103,7 +106,7 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
setOnMenuItemClickListener(this@CirclePlayerFragment)
ToolbarContentTintHelper.colorizeToolbar(
this,
ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal),
colorControlNormal(),
requireActivity()
)
}
@ -116,11 +119,17 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
ThemeStore.accentColor(requireContext()),
false
)
binding.volumeSeekBar.progressColor = accentColor()
binding.volumeSeekBar.arcColor = ColorUtil.withAlpha(accentColor(), 0.25f)
binding.volumeSeekBar.circleProgressColor = accentColor()
binding.volumeSeekBar.circleColor = ColorUtil.withAlpha(accentColor(), 0.25f)
setUpPlayPauseFab()
setUpPrevNext()
setUpPlayerToolbar()
binding.albumCoverOverlay.background = ColorDrawable(
MaterialValueHelper.getPrimaryTextColor(
requireContext(),
accentColor().isColorLight
)
)
}
private fun setUpPrevNext() {
@ -144,6 +153,17 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
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() {
super.onResume()
progressViewUpdateHelper.start()
@ -153,11 +173,11 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
audioVolumeObserver?.register(AudioManager.STREAM_MUSIC, this)
val audioManager = audioManager
if (audioManager != null) {
binding.volumeSeekBar.max = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
binding.volumeSeekBar.progress = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
}
binding.volumeSeekBar.setOnSeekArcChangeListener(this)
binding.volumeSeekBar.max =
audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC).toFloat()
binding.volumeSeekBar.progress =
audioManager.getStreamVolume(AudioManager.STREAM_MUSIC).toFloat()
binding.volumeSeekBar.setOnSeekBarChangeListener(this)
}
override fun onPause() {
@ -178,7 +198,7 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
override fun onBackPressed(): Boolean = false
override fun toolbarIconColor(): Int =
ATHUtil.resolveColor(requireContext(), android.R.attr.colorControlNormal)
colorControlNormal()
override val paletteColor: Int
get() = Color.BLACK
@ -191,6 +211,11 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
override fun onPlayStateChanged() {
updatePlayPauseDrawableState()
if (MusicPlayerRemote.isPlaying) {
if (rotateAnimator?.isStarted == true) rotateAnimator?.resume() else rotateAnimator?.start()
} else {
rotateAnimator?.pause()
}
}
override fun onPlayingMetaChanged() {
@ -202,6 +227,7 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
super.onServiceConnected()
updateSong()
updatePlayPauseDrawableState()
setupRotateAnimation()
}
private fun updateSong() {
@ -215,6 +241,16 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
} else {
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() {
@ -225,8 +261,8 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
}
override fun onAudioVolumeChanged(currentVolume: Int, maxVolume: Int) {
_binding?.volumeSeekBar?.max = maxVolume
_binding?.volumeSeekBar?.progress = currentVolume
_binding?.volumeSeekBar?.max = maxVolume.toFloat()
_binding?.volumeSeekBar?.progress = currentVolume.toFloat()
}
override fun onDestroyView() {
@ -237,15 +273,16 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
_binding = null
}
override fun onProgressChanged(seekArc: SeekArc?, progress: Int, fromUser: Boolean) {
override fun onProgressChanged(seekBar: CircularSeekBar?, progress: Float, fromUser: Boolean) {
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() {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -26,14 +26,13 @@ import android.view.MenuItem
import android.view.View
import android.view.animation.DecelerateInterpolator
import android.view.animation.LinearInterpolator
import android.widget.PopupMenu
import android.widget.SeekBar
import androidx.appcompat.widget.PopupMenu
import androidx.lifecycle.lifecycleScope
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentFullPlayerControlsBinding
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.applyColor
import code.name.monkey.retromusic.extensions.getSongInfo
@ -205,6 +204,7 @@ class FullPlaybackControlsFragment :
popupMenu.setOnMenuItemClickListener(this)
popupMenu.inflate(R.menu.menu_player)
popupMenu.menu.findItem(R.id.action_toggle_favorite).isVisible = false
popupMenu.menu.findItem(R.id.action_toggle_lyrics).isChecked = PreferenceUtil.showLyrics
popupMenu.show()
}
}
@ -317,28 +317,24 @@ class FullPlaybackControlsFragment :
fun updateIsFavorite(animate: Boolean = false) {
lifecycleScope.launch(Dispatchers.IO) {
val playlist: PlaylistEntity = libraryViewModel.favoritePlaylist()
if (playlist != null) {
val song: SongEntity =
MusicPlayerRemote.currentSong.toSongEntity(playlist.playListId)
val isFavorite: Boolean = libraryViewModel.isFavoriteSong(song).isNotEmpty()
withContext(Dispatchers.Main) {
val icon = if (animate) {
if (isFavorite) R.drawable.avd_favorite else R.drawable.avd_unfavorite
} else {
if (isFavorite) R.drawable.ic_favorite else R.drawable.ic_favorite_border
}
val drawable: Drawable = RetroUtil.getTintedVectorDrawable(
requireContext(),
icon,
Color.WHITE
)
binding.songFavourite.apply {
setImageDrawable(drawable)
getDrawable().also {
if (it is AnimatedVectorDrawable) {
it.start()
}
val isFavorite: Boolean =
libraryViewModel.isSongFavorite(MusicPlayerRemote.currentSong.id)
withContext(Dispatchers.Main) {
val icon = if (animate) {
if (isFavorite) R.drawable.avd_favorite else R.drawable.avd_unfavorite
} else {
if (isFavorite) R.drawable.ic_favorite else R.drawable.ic_favorite_border
}
val drawable: Drawable = RetroUtil.getTintedVectorDrawable(
requireContext(),
icon,
Color.WHITE
)
binding.songFavourite.apply {
setImageDrawable(drawable)
getDrawable().also {
if (it is AnimatedVectorDrawable) {
it.start()
}
}
}

View file

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

View file

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

View file

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

View file

@ -16,15 +16,21 @@ package code.name.monkey.retromusic.fragments.player.normal
import android.animation.ArgbEvaluator
import android.animation.ValueAnimator
import android.content.SharedPreferences
import android.graphics.drawable.GradientDrawable
import android.os.Bundle
import android.view.View
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.retromusic.R
import code.name.monkey.retromusic.SNOWFALL
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.surfaceColor
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment
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.views.DrawableGradient
class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player) {
class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player),
SharedPreferences.OnSharedPreferenceChangeListener {
private var lastColor: Int = 0
override val paletteColor: Int
@ -54,7 +61,7 @@ class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player) {
valueAnimator = ValueAnimator.ofObject(
ArgbEvaluator(),
ATHUtil.resolveColor(requireContext(), R.attr.colorSurface),
surfaceColor(),
i
)
valueAnimator?.addUpdateListener { animation ->
@ -63,7 +70,7 @@ class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player) {
GradientDrawable.Orientation.TOP_BOTTOM,
intArrayOf(
animation.animatedValue as Int,
ATHUtil.resolveColor(requireContext(), R.attr.colorSurface)
surfaceColor()
), 0
)
binding.colorGradientBackground.background = drawable
@ -85,9 +92,7 @@ class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player) {
return false
}
override fun toolbarIconColor(): Int {
return ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal)
}
override fun toolbarIconColor() = colorControlNormal()
override fun onColorChanged(color: MediaNotificationProcessor) {
controlsFragment.setColor(color)
@ -96,7 +101,7 @@ class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player) {
ToolbarContentTintHelper.colorizeToolbar(
binding.playerToolbar,
ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal),
colorControlNormal(),
requireActivity()
)
@ -121,11 +126,23 @@ class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player) {
_binding = FragmentPlayerBinding.bind(view)
setUpSubFragments()
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()
}
override fun onDestroyView() {
super.onDestroyView()
PreferenceManager.getDefaultSharedPreferences(requireContext())
.unregisterOnSharedPreferenceChangeListener(this)
_binding = null
}
@ -145,11 +162,21 @@ class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player) {
ToolbarContentTintHelper.colorizeToolbar(
binding.playerToolbar,
ATHUtil.resolveColor(requireContext(), R.attr.colorControlNormal),
colorControlNormal(),
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() {
updateIsFavorite()
}

View file

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

View file

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

View file

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

View file

@ -281,12 +281,13 @@ class TinyPlayerFragment : AbsPlayerFragment(R.layout.fragment_tiny_player),
return gestureDetector.onTouchEvent(event)
}
@Suppress("Deprecation")
private fun vibrate() {
val v = requireContext().getSystemService(Context.VIBRATOR_SERVICE) as Vibrator?
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 {
v!!.vibrate(10)
v?.vibrate(10)
}
}
}

View file

@ -42,7 +42,7 @@ class PlaylistsFragment :
super.onViewCreated(view, savedInstanceState)
libraryViewModel.getPlaylists().observe(viewLifecycleOwner, {
if (it.isNotEmpty())
adapter?.swapDataSet(it.filter { playlistWithSongs-> playlistWithSongs.songs.isNotEmpty() })
adapter?.swapDataSet(it)
else
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.textfield.TextInputEditText
import com.google.android.material.transition.MaterialSharedAxis
import net.yslibrary.android.keyboardvisibilityevent.KeyboardVisibilityEvent
import java.util.*
import kotlin.collections.ArrayList
@ -70,7 +71,10 @@ class SearchFragment : AbsMainActivityFragment(R.layout.fragment_search), TextWa
setupRecyclerView()
binding.voiceSearch.setOnClickListener { startMicSearch() }
binding.clearText.setOnClickListener { binding.searchView.clearText() }
binding.clearText.setOnClickListener {
binding.searchView.clearText()
searchAdapter.swapDataSet(listOf())
}
binding.searchView.apply {
addTextChangedListener(this@SearchFragment)
focusAndShowKeyboard()
@ -97,6 +101,13 @@ class SearchFragment : AbsMainActivityFragment(R.layout.fragment_search), TextWa
bottomMargin = it
}
})
KeyboardVisibilityEvent.setEventListener(requireActivity(), viewLifecycleOwner) {
if (it) {
binding.keyboardPopup.isGone = true
} else {
binding.keyboardPopup.show()
}
}
binding.appBarLayout.statusBarForeground =
MaterialShapeDrawable.createWithElevationOverlay(requireContext())
}

View file

@ -16,16 +16,19 @@ package code.name.monkey.retromusic.fragments.settings
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Build
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceManager
import code.name.monkey.appthemehelper.common.prefs.supportv7.ATEPreferenceFragmentCompat
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.preferences.*
import code.name.monkey.retromusic.util.NavigationUtil
@ -67,14 +70,23 @@ abstract class AbsSettingsFragment : ATEPreferenceFragmentCompat() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
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
// So we get insets from root view
// So we get insets from decor view
// https://github.com/material-components/material-components-android/issues/1310
ViewCompat.setOnApplyWindowInsetsListener(
view
) { _, insets ->
listView.updatePadding(bottom = insets.safeGetBottomInsets())
insets
requireActivity().rootView
) { _, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
listView.updatePadding(
left = insets.left,
bottom = insets.bottom,
right = insets.right,
)
windowInsets
}
invalidateSettings()
}

View file

@ -19,6 +19,8 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
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.R
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.rootView
import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.util.NavigationUtil
import code.name.monkey.retromusic.util.RetroUtil
class MainSettingsFragment : Fragment(), View.OnClickListener {
@ -87,8 +88,17 @@ class MainSettingsFragment : Fragment(), View.OnClickListener {
binding.buyPremium.setTextColor(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.GlideOption
import com.bumptech.glide.annotation.GlideType
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.Key
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.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 java.io.File
@ -116,6 +122,16 @@ object RetroGlideExtension {
.signature(createSignature(song))
}
@JvmStatic
@GlideOption
fun simpleSongCoverOptions(
baseRequestOptions: BaseRequestOptions<*>,
song: Song
): BaseRequestOptions<*> {
return baseRequestOptions.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
.signature(createSignature(song))
}
@JvmStatic
@GlideOption
fun albumCoverOptions(
@ -194,4 +210,33 @@ object RetroGlideExtension {
fun <TranscodeType> getDefaultTransition(): GenericTransitionOptions<TranscodeType> {
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.widget.ImageView
import code.name.monkey.appthemehelper.util.ATHUtil
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.BitmapPaletteWrapper
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) {
protected val defaultFooterColor: Int
get() = ATHUtil.resolveColor(getView().context, R.attr.colorControlNormal)
get() = getView().context.colorControlNormal()
abstract fun onColorReady(colors: MediaNotificationProcessor)

View file

@ -14,6 +14,7 @@
package code.name.monkey.retromusic.glide.audiocover;
import org.jaudiotagger.audio.exceptions.CannotReadException;
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
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
} catch (ReadOnlyFileException | InvalidAudioFrameException | TagException | IOException ignored) {
} catch (ReadOnlyFileException | InvalidAudioFrameException | TagException | IOException | CannotReadException ignored) {
}
// 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.os.Environment
import android.util.Log
import android.widget.Toast
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.BuildConfig
import code.name.monkey.retromusic.helper.BackupContent.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.*
@ -24,10 +28,11 @@ object BackupHelper {
zipItems.addAll(getSettingsZipItems(context))
getUserImageZipItems(context)?.let { zipItems.addAll(it) }
zipItems.addAll(getCustomArtistZipItems(context))
zipItems.addAll(getQueueZipItems(context))
zipAll(zipItems, backupFile)
}
private suspend fun zipAll(zipItems: List<ZipItem>, backupFile: File) {
private suspend fun zipAll(zipItems: List<ZipItem>, backupFile: File) =
withContext(Dispatchers.IO) {
kotlin.runCatching {
ZipOutputStream(BufferedOutputStream(FileOutputStream(backupFile))).use { out ->
@ -42,27 +47,42 @@ object BackupHelper {
}
}
}.onFailure {
it.printStackTrace()
withContext(Dispatchers.Main) {
Toast.makeText(App.getContext(), "Couldn't create backup", Toast.LENGTH_SHORT)
.show()
}
throw Exception(it)
}.onSuccess {
withContext(Dispatchers.Main) {
Toast.makeText(
App.getContext(),
"Backup created successfully",
Toast.LENGTH_SHORT
)
.show()
}
}
withContext(Dispatchers.Main) {
Toast.makeText(App.getContext(), "Backup created successfully", Toast.LENGTH_SHORT)
.show()
}
}
}
private fun getDatabaseZipItems(context: Context): List<ZipItem> {
return context.databaseList().filter {
it.endsWith(".db")
it.endsWith(".db") && it != queueDatabase
}.map {
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> {
val sharedPrefPath = context.filesDir.parentFile?.absolutePath + "/shared_prefs/"
return listOf(
@ -94,33 +114,47 @@ object BackupHelper {
)
}?.toList() ?: listOf()
)
zipItemList.add(
ZipItem(
sharedPrefPath + File.separator + "custom_artist_image.xml",
"$CUSTOM_ARTISTS_PATH${File.separator}prefs${File.separator}custom_artist_image.xml"
)
)
File(sharedPrefPath + File.separator + "custom_artist_image.xml").let {
if (it.exists()) {
zipItemList.add(
ZipItem(
it.absolutePath,
"$CUSTOM_ARTISTS_PATH${File.separator}prefs${File.separator}custom_artist_image.xml"
)
)
}
}
return zipItemList
}
suspend fun restoreBackup(context: Context, inputStream: InputStream?) {
suspend fun restoreBackup(
context: Context,
inputStream: InputStream?,
contents: List<BackupContent>
) {
withContext(Dispatchers.IO) {
ZipInputStream(inputStream).use {
var entry = it.nextEntry
while (entry != null) {
if (entry.isDatabaseEntry()) restoreDatabase(context, it, entry)
if (entry.isPreferenceEntry()) restorePreferences(context, it, entry)
if (entry.isImageEntry()) restoreImages(context, it, entry)
if (entry.isCustomArtistImageEntry()) restoreCustomArtistImages(
context,
it,
entry
)
if (entry.isCustomArtistPrefEntry()) restoreCustomArtistPrefs(
context,
it,
entry
)
if (entry.isDatabaseEntry() && contents.contains(PLAYLISTS)) {
restoreDatabase(context, it, entry)
} else if (entry.isPreferenceEntry() && contents.contains(SETTINGS)) {
restorePreferences(context, it, entry)
} else if (entry.isImageEntry() && contents.contains(USER_IMAGES)) {
restoreImages(context, it, entry)
} else if (entry.isCustomArtistImageEntry() && contents.contains(
CUSTOM_ARTIST_IMAGES
)
) {
restoreCustomArtistImages(context, it, entry)
restoreCustomArtistPrefs(context, it, entry)
} else if (entry.isQueueEntry() && contents.contains(QUEUE)) {
restoreQueueDatabase(context, it, entry)
}
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(
context: Context,
zipIn: ZipInputStream,
@ -218,10 +267,12 @@ object BackupHelper {
const val BACKUP_EXTENSION = "rmbak"
const val APPEND_EXTENSION = ".$BACKUP_EXTENSION"
private const val DATABASES_PATH = "databases"
private const val QUEUE_PATH = "queue"
private const val SETTINGS_PATH = "prefs"
private const val IMAGES_PATH = "userImages"
private const val CUSTOM_ARTISTS_PATH = "artistImages"
private const val THEME_PREFS_KEY_DEFAULT = "[[kabouzeid_app-theme-helper]]"
private const val queueDatabase = "music_playback_state.db"
private fun ZipEntry.isDatabaseEntry(): Boolean {
return name.startsWith(DATABASES_PATH)
@ -243,6 +294,10 @@ object BackupHelper {
return name.startsWith(CUSTOM_ARTISTS_PATH) && name.contains("prefs")
}
private fun ZipEntry.isQueueEntry(): Boolean {
return name.startsWith(QUEUE_PATH)
}
private fun ZipEntry.getFileName(): String {
return name.substring(name.lastIndexOf(File.separator))
}
@ -261,4 +316,12 @@ fun CharSequence.sanitize(): String {
.replace("|", "_")
.replace("\\", "_")
.replace("&", "_")
}
enum class BackupContent {
SETTINGS,
USER_IMAGES,
CUSTOM_ARTIST_IMAGES,
PLAYLISTS,
QUEUE
}

View file

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

View file

@ -17,7 +17,7 @@ package code.name.monkey.retromusic.helper.menu
import android.content.Intent
import android.view.MenuItem
import android.view.View
import android.widget.PopupMenu
import androidx.appcompat.widget.PopupMenu
import androidx.core.os.bundleOf
import androidx.fragment.app.FragmentActivity
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 playlists(): Home
suspend fun homeSections(): List<Home>
@ExperimentalCoroutinesApi
suspend fun homeSectionsFlow(): Flow<Result<List<Home>>>
suspend fun playlist(playlistId: Long): Playlist
@ -106,6 +107,7 @@ interface Repository {
suspend fun searchArtists(query: String): List<Artist>
suspend fun searchSongs(query: String): List<Song>
suspend fun searchAlbums(query: String): List<Album>
suspend fun isSongFavorite(songId: Long): Boolean
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 isSongFavorite(songId: Long): Boolean =
roomRepository.isSongFavorite(context, songId)
override fun getSongByGenre(genreId: Long): Song = genreRepository.song(genreId)
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 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()

View file

@ -1,7 +1,9 @@
package code.name.monkey.retromusic.repository
import android.content.Context
import androidx.annotation.WorkerThread
import androidx.lifecycle.LiveData
import code.name.monkey.retromusic.R
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_SONG_COUNT
@ -45,6 +47,7 @@ interface RoomRepository {
suspend fun insertBlacklistPathAsync(blackListStoreEntity: BlackListStoreEntity)
suspend fun blackListPaths(): List<BlackListStoreEntity>
suspend fun deleteSongs(songs: List<Song>)
suspend fun isSongFavorite(context: Context, songId: Long): Boolean
}
class RealRoomRepository(
@ -60,7 +63,7 @@ class RealRoomRepository(
@WorkerThread
override suspend fun checkPlaylistExists(playlistName: String): List<PlaylistEntity> =
playlistDao.isPlaylistExists(playlistName)
playlistDao.playlist(playlistName)
@WorkerThread
override suspend fun playlists(): List<PlaylistEntity> = playlistDao.playlists()
@ -111,12 +114,13 @@ class RealRoomRepository(
}
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) {
playlist
} else {
println("Playlist Created")
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>> =
playlistDao.favoritesSongsLiveData(
playlistDao.isPlaylistExists(favorite).first().playListId
playlistDao.playlist(favorite).first().playListId
)
override suspend fun favoritePlaylistSongs(favorite: String): List<SongEntity> =
if (playlistDao.isPlaylistExists(favorite).isNotEmpty()) playlistDao.favoritesSongs(
playlistDao.isPlaylistExists(favorite).first().playListId
if (playlistDao.playlist(favorite).isNotEmpty()) playlistDao.favoritesSongs(
playlistDao.playlist(favorite).first().playListId
) else emptyList()
override suspend fun insertSongInPlayCount(playCountEntity: PlayCountEntity) =
@ -192,4 +196,12 @@ class RealRoomRepository(
blackListStoreDao.deleteBlacklistPath(blackListStoreEntity)
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.TOGGLE_HEADSET;
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.PendingIntent;
@ -369,6 +371,7 @@ public class MusicService extends MediaBrowserServiceCompat
private PowerManager.WakeLock wakeLock;
private NotificationManager notificationManager;
private boolean isForeground = false;
private int notifyMode = NOTIFY_MODE_BACKGROUND;
private static Bitmap copy(Bitmap bitmap) {
Bitmap.Config config = bitmap.getConfig();
@ -938,8 +941,9 @@ public class MusicService extends MediaBrowserServiceCompat
updateNotification();
break;
case CLASSIC_NOTIFICATION:
initNotification();
updateNotification();
playingNotification.setPlaying(isPlaying());
playingNotification.updateMetadata(getCurrentSong(), this::startForegroundOrNotify);
break;
case TOGGLE_HEADSET:
registerHeadsetEvents();
@ -1456,9 +1460,18 @@ public class MusicService extends MediaBrowserServiceCompat
}
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.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (VersionUtils.hasQ()) {
startForeground(
PlayingNotification.NOTIFICATION_ID, playingNotification.build(),
ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
@ -1474,6 +1487,7 @@ public class MusicService extends MediaBrowserServiceCompat
PlayingNotification.NOTIFICATION_ID, playingNotification.build()
);
}
notifyMode = newNotifyMode;
return Unit.INSTANCE;
}

View file

@ -37,6 +37,8 @@ abstract class PlayingNotification(context: Context) :
const val NOTIFICATION_CONTROLS_SIZE_MULTIPLIER = 1.0f
internal const val NOTIFICATION_CHANNEL_ID = "playing_notification"
const val NOTIFICATION_ID = 1
const val NOTIFY_MODE_FOREGROUND = 1
const val NOTIFY_MODE_BACKGROUND = 0
@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.retromusic.R
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.RetroGlideExtension
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
@ -163,6 +161,7 @@ class PlayingNotificationImpl(
onUpdate()
}
})
updateFavorite(song, onUpdate)
}
private fun buildPlayAction(isPlaying: Boolean): NotificationCompat.Action {
@ -185,17 +184,28 @@ class PlayingNotificationImpl(
).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) {
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) {
GlobalScope.launch(Dispatchers.IO) {
val playlist: PlaylistEntity = MusicUtil.repository.favoritePlaylist()
val isFavorite = if (playlist != null) {
val songEntity = song.toSongEntity(playlist.playListId)
MusicUtil.repository.isFavoriteSong(songEntity).isNotEmpty()
} else false
val isFavorite = MusicUtil.repository.isSongFavorite(song.id)
withContext(Dispatchers.Main) {
mActions[0] = buildFavoriteAction(isFavorite)
onUpdate()

View file

@ -15,12 +15,7 @@
package code.name.monkey.retromusic.util
import android.app.Activity
import android.content.Context
import android.content.Intent
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
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 {
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
@ -655,4 +657,7 @@ object PreferenceUtil {
val materialYou
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
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.net.Uri
@ -32,14 +31,6 @@ class RingtoneManager(val context: Context) {
fun setRingtone(song: Song) {
val resolver = context.contentResolver
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 {
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.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import com.google.android.material.progressindicator.CircularProgressIndicator
object ViewUtil {

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