diff --git a/app/build.gradle b/app/build.gradle index c7161c505..12d4d2e6e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -33,7 +33,7 @@ android { 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,7 +95,7 @@ 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' @@ -105,12 +105,11 @@ dependencies { //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" @@ -153,12 +152,13 @@ dependencies { implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0' 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' } \ No newline at end of file diff --git a/app/src/debug/res/values/donottranslate.xml b/app/src/debug/res/values/donottranslate.xml index 879226a34..7d3f0ee62 100644 --- a/app/src/debug/res/values/donottranslate.xml +++ b/app/src/debug/res/values/donottranslate.xml @@ -1,4 +1,5 @@ true + false \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 377342a16..71977a668 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -22,7 +22,7 @@ + android:excludeFromRecents="false" + android:exported="true" + android:label="@string/restore" + android:theme="@style/Theme.RetroMusic.Dialog"> + + + + + + + + + + + + + + + + @@ -273,10 +296,9 @@ + android:label="@string/app_name"> diff --git a/app/src/main/assets/oldindex.html b/app/src/main/assets/oldindex.html index 367da632a..7f6f8d8f8 100644 --- a/app/src/main/assets/oldindex.html +++ b/app/src/main/assets/oldindex.html @@ -24,40 +24,48 @@ padding-top: 8px; } - - -

Phonograph by Karim Abou Zeid

-

Material Dialogs and Cab - by Aidan Michael Follestad

AOSP Support Librariesby AOSP contributors

+ title="AOSP Support Libraries">AOSP Support Libraries by AOSP contributors

Glide by Sam Judd

Retrofit by Square team

+

OkHttp by Square team

+

Koin by Arnaud Giuliani

+

Material Dialogs and Cab + by Aidan Michael Follestad

Material Contextual Action Bar by Aidan Michael Follestad

-

OkHttp by Square team

-

- CircleImageView by Henning Dodenhof

-

- MaterialProgressBar by Zhang Hai

Android In-App Billing v3 Library by Henning Dodenhof

Advanced RecyclerView by Haruki Hasegawa

-

Android-ObservableScrollView by Soichiro - Kashima

+

Custom Activity on Crash by Eduard Ereza Martínez +

+

NanoHttpd by NanoHttpd Team

+

Circular Seekbar by Tankery

+

jAudioTagger by Kanedias

+

Android Fast Scroll by Zhang Hai

+

Image Picker by Dhaval Patel

+

Material Intro by Jan Heinrich Reimer

+

Slidr by Drew Heavner

Icons by Austin Andrews

Material Design City Wallpaper

- diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/DriveModeActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/DriveModeActivity.kt index 1205d1bdb..9bdfbd05d 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/DriveModeActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/DriveModeActivity.kt @@ -21,7 +21,6 @@ import android.graphics.PorterDuff import android.os.Bundle import android.view.animation.LinearInterpolator import android.widget.SeekBar -import androidx.activity.viewModels import androidx.lifecycle.lifecycleScope import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.retromusic.R @@ -29,8 +28,6 @@ import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity import code.name.monkey.retromusic.databinding.ActivityDriveModeBinding import code.name.monkey.retromusic.db.toSongEntity import code.name.monkey.retromusic.extensions.drawAboveSystemBars -import code.name.monkey.retromusic.fragments.LibraryViewModel -import code.name.monkey.retromusic.fragments.ReloadType import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment import code.name.monkey.retromusic.glide.BlurTransformation import code.name.monkey.retromusic.glide.GlideApp @@ -50,7 +47,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.koin.android.ext.android.inject -import org.koin.androidx.viewmodel.ext.android.viewModel /** diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/LicenseActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/LicenseActivity.kt index 11cd201ca..7169a5271 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/LicenseActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/LicenseActivity.kt @@ -16,8 +16,6 @@ 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 @@ -25,29 +23,30 @@ 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 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(R.id.toolbar) - setSupportActionBar(toolbar) - ToolbarContentTintHelper.colorBackButton(toolbar) - toolbar.setBackgroundColor(resolveColor(this, R.attr.colorSurface)) - val webView = findViewById(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) + 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) @@ -72,12 +71,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( "

Unable to load

" + e.localizedMessage + "

", "text/html", "UTF-8" ) } + binding.license.drawAboveSystemBars() } override fun onOptionsItemSelected(item: MenuItem): Boolean { diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/PermissionActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/PermissionActivity.kt index 8a5d75431..9a63bd49c 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/PermissionActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/PermissionActivity.kt @@ -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)) } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/SettingsActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/SettingsActivity.kt index 23ae725a3..cfe0815c4 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/SettingsActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/SettingsActivity.kt @@ -34,12 +34,16 @@ class SettingsActivity : AbsThemeActivity(), ColorCallback, OnThemeChangedListen override fun onCreate(savedInstanceState: Bundle?) { val mSavedInstanceState = extra(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) @@ -81,7 +85,6 @@ class SettingsActivity : AbsThemeActivity(), ColorCallback, OnThemeChangedListen ThemeStore.editTheme(this).accentColor(color).commit() if (VersionUtils.hasNougatMR()) DynamicShortcutManager(this).updateDynamicShortcuts() - restart() } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/WhatsNewActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/WhatsNewActivity.kt index 40200eba0..b1c979076 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/WhatsNewActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/WhatsNewActivity.kt @@ -17,6 +17,7 @@ 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.drawAboveSystemBars import code.name.monkey.retromusic.extensions.setLightStatusBarAuto import code.name.monkey.retromusic.extensions.setTaskDescriptionColorAuto import code.name.monkey.retromusic.util.PreferenceUtil.lastVersion @@ -38,12 +39,12 @@ class WhatsNewActivity : AbsThemeActivity() { 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) @@ -100,6 +101,7 @@ class WhatsNewActivity : AbsThemeActivity() { binding.tgFab.extend() } } + binding.webView.drawAboveSystemBars() } companion object { diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt index b80df4a60..62b23ec8d 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt @@ -14,12 +14,15 @@ */ 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 @@ -28,7 +31,6 @@ import androidx.core.view.isGone import androidx.core.view.isVisible 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 +63,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,14 +82,27 @@ 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 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) { @@ -131,6 +148,7 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { updateColor() binding.slidingPanel.backgroundTintList = ColorStateList.valueOf(darkAccentColor()) bottomNavigationView.backgroundTintList = ColorStateList.valueOf(darkAccentColor()) + navigationBarColor = surfaceColor() } private fun setupBottomSheet() { @@ -177,9 +195,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) @@ -252,18 +286,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) { @@ -273,10 +314,7 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { } else { setLightStatusBar( ColorUtil.isColorLight( - ATHUtil.resolveColor( - this, - android.R.attr.windowBackground - ) + surfaceColor() ) ) setLightNavigationBar(true) diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AbsTagEditorActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AbsTagEditorActivity.kt index cee65c013..8cf8cbd40 100755 --- a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AbsTagEditorActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AbsTagEditorActivity.kt @@ -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 : AbsBaseActivity() { - abstract val editorImage: ImageView? + abstract val editorImage: ImageView val repository by inject() lateinit var saveFab: MaterialButton protected var id: Long = 0 private set private var paletteColorPrimary: Int = 0 - private var isInNoImageMode: Boolean = false private var songPaths: List? = null private var savedSongPaths: List? = null private val currentSongPath: String? = null @@ -176,6 +174,15 @@ abstract class AbsTagEditorActivity : 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 : 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 : 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 : 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) } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AlbumTagEditorActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AlbumTagEditorActivity.kt index 18b73ed29..dfefadc8e 100755 --- a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AlbumTagEditorActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AlbumTagEditorActivity.kt @@ -29,9 +29,11 @@ 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.isColorLight import code.name.monkey.retromusic.extensions.setTint import code.name.monkey.retromusic.glide.GlideApp import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper @@ -63,41 +65,6 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(binding.editorImage) { - override fun onResourceReady( - resource: BitmapPaletteWrapper, - transition: Transition? - ) { - getColor(resource.palette, Color.TRANSPARENT) - albumArtBitmap = resource.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) } - setImageBitmap( - albumArtBitmap, - getColor( - resource.palette, - 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 @@ -171,6 +138,41 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(binding.editorImage) { + override fun onResourceReady( + resource: BitmapPaletteWrapper, + transition: Transition? + ) { + getColor(resource.palette, Color.TRANSPARENT) + albumArtBitmap = resource.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) } + setImageBitmap( + albumArtBitmap, + getColor( + resource.palette, + 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?) {} + }) + } + override fun save() { val fieldKeyValueMap = EnumMap(FieldKey::class.java) fieldKeyValueMap[FieldKey.ALBUM] = binding.albumText.text.toString() @@ -213,6 +215,16 @@ class AlbumTagEditorActivity : AbsTagEditorActivity private val songRepository by inject() + 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 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 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 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), + ATHUtil.resolveColor(this, R.attr.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), + ATHUtil.resolveColor(this, R.attr.defaultFooterColor) + ) + deleteAlbumArt = true + dataChanged() + } + + override fun setColors(color: Int) { + super.setColors(color) + saveFab.backgroundTintList = ColorStateList.valueOf(color) + ColorStateList.valueOf( + MaterialValueHelper.getPrimaryTextColor( + this, + color.isColorLight + ) + ).also { + saveFab.iconTint = it + saveFab.setTextColor(it) + } + } override fun save() { val fieldKeyValueMap = EnumMap(FieldKey::class.java) @@ -104,10 +156,16 @@ class SongTagEditorActivity : AbsTagEditorActivity 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 = listOf(songRepository.song(id).data) @@ -115,6 +173,38 @@ class SongTagEditorActivity : AbsTagEditorActivity override fun getSongUris(): List = listOf(MusicUtil.getSongFileUri(id)) override fun loadImageFromFile(selectedFile: Uri?) { + GlideApp.with(this@SongTagEditorActivity).asBitmapPalette().load(selectedFile) + .diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true) + .into(object : ImageViewTarget(binding.editorImage) { + override fun onResourceReady( + resource: BitmapPaletteWrapper, + transition: Transition? + ) { + RetroColorUtil.getColor(resource.palette, Color.TRANSPARENT) + albumArtBitmap = resource.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) } + setImageBitmap( + albumArtBitmap, + RetroColorUtil.getColor( + resource.palette, + ATHUtil.resolveColor( + this@SongTagEditorActivity, + R.attr.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 +221,6 @@ class SongTagEditorActivity : AbsTagEditorActivity val TAG: String = SongTagEditorActivity::class.java.simpleName } - override val editorImage: ImageView? - get() = null + override val editorImage: ImageView + get() = binding.editorImage } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/TagWriter.kt b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/TagWriter.kt index 4fcea20f2..9bc20f100 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/TagWriter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/TagWriter.kt @@ -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() } diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/SearchAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/SearchAdapter.kt index d7fb4e1fb..221239efb 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/SearchAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/SearchAdapter.kt @@ -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.* diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/backup/BackupAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/backup/BackupAdapter.kt index 3e32bf0e3..81b2bbf83 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/backup/BackupAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/backup/BackupAdapter.kt @@ -4,9 +4,9 @@ import android.view.LayoutInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup -import androidx.appcompat.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 diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/ImportPlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/ImportPlaylistDialog.kt index 1429aa5a1..2d16b223b 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/ImportPlaylistDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/ImportPlaylistDialog.kt @@ -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() diff --git a/app/src/main/java/code/name/monkey/retromusic/extensions/ActivityExtensions.kt b/app/src/main/java/code/name/monkey/retromusic/extensions/ActivityExtensions.kt index ff33f4ad2..3ccc81f4a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/extensions/ActivityExtensions.kt +++ b/app/src/main/java/code/name/monkey/retromusic/extensions/ActivityExtensions.kt @@ -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 Activity.extraNotNull(key: String, default: T? = nu fun Activity.dip(@DimenRes id: Int): Int { return resources.getDimensionPixelSize(id) -} \ No newline at end of file +} + +inline val Activity.rootView: View get() = findViewById(R.id.content).getChildAt(0) \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/extensions/ActivityThemeExtensions.kt b/app/src/main/java/code/name/monkey/retromusic/extensions/ActivityThemeExtensions.kt index adedcdba2..5a543924c 100644 --- a/app/src/main/java/code/name/monkey/retromusic/extensions/ActivityThemeExtensions.kt +++ b/app/src/main/java/code/name/monkey/retromusic/extensions/ActivityThemeExtensions.kt @@ -9,6 +9,7 @@ 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.util.ATHUtil @@ -46,7 +47,6 @@ fun AppCompatActivity.exitFullscreen() { } } - fun AppCompatActivity.hideStatusBar() { hideStatusBar(PreferenceUtil.isFullScreenMode) } @@ -54,16 +54,25 @@ fun AppCompatActivity.hideStatusBar() { private fun AppCompatActivity.hideStatusBar(fullscreen: Boolean) { val statusBar = window.decorView.rootView.findViewById(R.id.status_bar) if (statusBar != null) { - statusBar.visibility = if (fullscreen) View.GONE else View.VISIBLE + statusBar.isGone = fullscreen } } fun AppCompatActivity.setDrawBehindSystemBars() { WindowCompat.setDecorFitsSystemWindows(window, false) - window.statusBarColor = Color.TRANSPARENT - window.navigationBarColor = Color.TRANSPARENT - if (VersionUtils.hasQ()) { - window.isNavigationBarContrastEnforced = false + if (VersionUtils.hasOreo()) { + if (VersionUtils.hasQ()) { + window.isNavigationBarContrastEnforced = false + } + setNavigationBarColor(Color.TRANSPARENT) + setStatusBarColor(Color.TRANSPARENT) + } else { + setNavigationBarColorPreOreo(surfaceColor()) + if (VersionUtils.hasMarshmallow()) { + setStatusBarColor(Color.TRANSPARENT) + } else { + setStatusBarColor(surfaceColor()) + } } } @@ -102,7 +111,11 @@ fun AppCompatActivity.setLightStatusBarAuto(bgColor: Int) { } fun AppCompatActivity.setLightNavigationBar(enabled: Boolean) { - ATH.setLightNavigationBar(this, enabled) + ATH.setLightNavigationBar(this, enabled) +} + +fun AppCompatActivity.setLightNavigationBarAuto(bgColor: Int) { + setLightNavigationBar(ColorUtil.isColorLight(bgColor)) } @@ -136,4 +149,32 @@ 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)) +} + +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(R.id.status_bar) + if (statusBar != null) { + statusBar.setBackgroundColor( + ColorUtil.darkenColor( + color + ) + ) + } else { + window.statusBarColor = ColorUtil.darkenColor(color) + } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/extensions/CursorExtensions.kt b/app/src/main/java/code/name/monkey/retromusic/extensions/CursorExtensions.kt index a72eb155c..531c2e0bb 100644 --- a/app/src/main/java/code/name/monkey/retromusic/extensions/CursorExtensions.kt +++ b/app/src/main/java/code/name/monkey/retromusic/extensions/CursorExtensions.kt @@ -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) } diff --git a/app/src/main/java/code/name/monkey/retromusic/extensions/FragmentExtensions.kt b/app/src/main/java/code/name/monkey/retromusic/extensions/FragmentExtensions.kt index 07ce4d3a0..734b60243 100644 --- a/app/src/main/java/code/name/monkey/retromusic/extensions/FragmentExtensions.kt +++ b/app/src/main/java/code/name/monkey/retromusic/extensions/FragmentExtensions.kt @@ -66,7 +66,6 @@ val FragmentManager.currentNavigationFragment: Fragment? fun AppCompatActivity.currentFragment(navHostId: Int): Fragment? { val navHostFragment: NavHostFragment = supportFragmentManager.findFragmentById(navHostId) as NavHostFragment - navHostFragment.targetFragment return navHostFragment.childFragmentManager.fragments.firstOrNull() } diff --git a/app/src/main/java/code/name/monkey/retromusic/extensions/FragmentMusicExtensions.kt b/app/src/main/java/code/name/monkey/retromusic/extensions/FragmentMusicExtensions.kt index c1998b6b0..2541b8802 100644 --- a/app/src/main/java/code/name/monkey/retromusic/extensions/FragmentMusicExtensions.kt +++ b/app/src/main/java/code/name/monkey/retromusic/extensions/FragmentMusicExtensions.kt @@ -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 diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumsFragment.kt index 320632de8..a7422951b 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumsFragment.kt @@ -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 diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/backup/BackupFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/backup/BackupFragment.kt index ab29cd830..170ae9387 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/backup/BackupFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/backup/BackupFragment.kt @@ -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") diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/backup/BackupViewModel.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/backup/BackupViewModel.kt index 920e70c91..f482cc915 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/backup/BackupViewModel.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/backup/BackupViewModel.kt @@ -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) { + BackupHelper.restoreBackup(activity, inputStream, contents) withContext(Dispatchers.Main) { val intent = Intent( activity, - activity::class.java + MainActivity::class.java ) activity.startActivity(intent) exitProcess(0) diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/backup/RestoreActivity.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/backup/RestoreActivity.kt index f54e697c2..feab240ea 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/backup/RestoreActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/backup/RestoreActivity.kt @@ -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() + 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" } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/folder/FoldersFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/folder/FoldersFragment.kt index a30a0993a..f0944c32d 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/folder/FoldersFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/folder/FoldersFragment.kt @@ -21,9 +21,9 @@ import android.os.Environment import android.text.Html import android.view.* import android.webkit.MimeTypeMap -import androidx.appcompat.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 diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenresFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenresFragment.kt index 1d675fb83..603717767 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenresFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenresFragment.kt @@ -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 diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/home/HomeFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/home/HomeFragment.kt index 647361a84..3390621d4 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/home/HomeFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/home/HomeFragment.kt @@ -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 @@ -87,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() { diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/other/DetailListFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/other/DetailListFragment.kt index b28d903c0..14df3272c 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/other/DetailListFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/other/DetailListFragment.kt @@ -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) diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/other/UserInfoFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/other/UserInfoFragment.kt index ea3653eba..38b040b6c 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/other/UserInfoFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/other/UserInfoFragment.kt @@ -134,12 +134,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) diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/PlayerAlbumCoverFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/PlayerAlbumCoverFragment.kt index e75ab76cb..027a773e0 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/PlayerAlbumCoverFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/PlayerAlbumCoverFragment.kt @@ -14,42 +14,36 @@ */ 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 import code.name.monkey.retromusic.util.LyricUtil import code.name.monkey.retromusic.util.PreferenceUtil 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 +64,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 +74,28 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe } private fun updateLyrics() { - lyrics = null - 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 data: String = LyricUtil.getStringFromLrc(lrcFile) - if (!TextUtils.isEmpty(data)) { - Lyrics.parse(song, data) - } else { - // Get Embedded Lyrics and check if they are Synchronized - val embeddedLyrics = try{ - AudioFileIO.read(File(song.data)).tagOrCreateDefault.getFirst(FieldKey.LYRICS) - } catch(e: Exception){ - null - } - if (AbsSynchronizedLyrics.isSynchronized(embeddedLyrics)) { - Lyrics.parse(song, embeddedLyrics) - } else { - null - } - } - } catch (err: FileNotFoundException) { - null - } catch (e: CannotReadException){ - null + binding.lyricsView.setLabel("Empty") + val song = MusicPlayerRemote.currentSong + when { + LyricUtil.isLrcOriginalFileExist(song.data) -> { + LyricUtil.getLocalLyricOriginalFile(song.data) + ?.let { binding.lyricsView.loadLrc(it) } } - withContext(Dispatchers.Main) { - this@PlayerAlbumCoverFragment.lyrics = lyrics + LyricUtil.isLrcFileExist(song.title, song.artistName) -> { + LyricUtil.getLocalLyricFile(song.title, song.artistName) + ?.let { binding.lyricsView.loadLrc(it) } + } + else -> { + binding.lyricsView.reset() } } } 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) @@ -210,14 +128,25 @@ 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)) { - lyricsLayout.isVisible = false + lrcView.isVisible = false + viewPager.isInvisible = false progressViewUpdateHelper?.stop() } else { - lyricsLayout.isVisible = true + lrcView.isVisible = true + viewPager.isInvisible = true progressViewUpdateHelper?.start() } + lrcView.apply { + setDraggable(true, object : CoverLrcView.OnPlayClickListener { + override fun onPlayClick(time: Long): Boolean { + MusicPlayerRemote.seekTo(time.toInt()) + MusicPlayerRemote.resumePlaying() + return true + } + }) + } // Go to lyrics activity when clicked lyrics - binding.playerLyricsLine2.setOnClickListener { + lrcView.setOnClickListener { goToLyrics(requireActivity()) } } @@ -227,10 +156,12 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe val nps = PreferenceUtil.nowPlayingScreen // Don't show lyrics container for below conditions if (nps == Circle || nps == Peak || nps == Tiny || !PreferenceUtil.showLyrics) { - lyricsLayout.isVisible = false + lrcView.isVisible = false + viewPager.isInvisible = false progressViewUpdateHelper?.stop() } else { - lyricsLayout.isVisible = true + lrcView.isVisible = true + viewPager.isInvisible = true progressViewUpdateHelper?.start() } PreferenceManager.getDefaultSharedPreferences(requireContext()) @@ -266,30 +197,42 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe 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 + lrcView.isVisible = true + viewPager.isInvisible = true progressViewUpdateHelper?.start() - lyricsLayout.animate().alpha(1f).duration = + lrcView.animate().alpha(1f).duration = AbsPlayerFragment.VISIBILITY_ANIM_DURATION - binding.playerLyrics.isVisible = true + } else { + lrcView.isVisible = false + viewPager.isInvisible = false + progressViewUpdateHelper?.stop() } } else { + lrcView.isVisible = false + viewPager.isInvisible = 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 updatePlayingQueue() { binding.viewPager.apply { adapter = AlbumCoverPagerAdapter(childFragmentManager, MusicPlayerRemote.playingQueue) @@ -321,6 +264,18 @@ 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) { diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/adaptive/AdaptiveFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/adaptive/AdaptiveFragment.kt index 225d7b208..32bb5c259 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/adaptive/AdaptiveFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/adaptive/AdaptiveFragment.kt @@ -47,7 +47,7 @@ class AdaptiveFragment : AbsPlayerFragment(R.layout.fragment_adaptive_player) { _binding = FragmentAdaptivePlayerBinding.bind(view) setUpSubFragments() setUpPlayerToolbar() - binding.root.drawAboveSystemBars() + binding.playbackControlsFragment.drawAboveSystemBars() } private fun setUpSubFragments() { diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/blur/BlurPlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/blur/BlurPlayerFragment.kt index aec5f2e57..675e85e4d 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/blur/BlurPlayerFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/blur/BlurPlayerFragment.kt @@ -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? = 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() { diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/cardblur/CardBlurFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/cardblur/CardBlurFragment.kt index bec78c7e5..f7161a0ec 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/cardblur/CardBlurFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/cardblur/CardBlurFragment.kt @@ -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? = 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() { diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/circle/CirclePlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/circle/CirclePlayerFragment.kt index 0d934398f..a7b3b8256 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/circle/CirclePlayerFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/circle/CirclePlayerFragment.kt @@ -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? = null + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) progressViewUpdateHelper = MusicProgressViewUpdateHelper(this) @@ -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() { @@ -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() { diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/full/FullPlaybackControlsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/full/FullPlaybackControlsFragment.kt index db751d52b..703052b17 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/full/FullPlaybackControlsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/full/FullPlaybackControlsFragment.kt @@ -26,14 +26,13 @@ import android.view.MenuItem import android.view.View import android.view.animation.DecelerateInterpolator import android.view.animation.LinearInterpolator -import androidx.appcompat.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 diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/gradient/GradientPlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/gradient/GradientPlayerFragment.kt index d24112406..ddfc188ee 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/gradient/GradientPlayerFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/gradient/GradientPlayerFragment.kt @@ -23,9 +23,9 @@ import android.graphics.drawable.AnimatedVectorDrawable import android.os.Bundle import android.view.View import android.view.animation.LinearInterpolator -import androidx.appcompat.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 diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/tiny/TinyPlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/tiny/TinyPlayerFragment.kt index e73992e0f..01cbdf665 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/tiny/TinyPlayerFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/tiny/TinyPlayerFragment.kt @@ -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) } } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/search/SearchFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/search/SearchFragment.kt index 4cd181288..9e909e4d0 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/search/SearchFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/search/SearchFragment.kt @@ -70,7 +70,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() diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/settings/AbsSettingsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/settings/AbsSettingsFragment.kt index ab3987a37..7a0c4ffe3 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/settings/AbsSettingsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/settings/AbsSettingsFragment.kt @@ -16,6 +16,7 @@ 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 @@ -26,6 +27,7 @@ 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,11 +69,15 @@ 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 decor view // https://github.com/material-components/material-components-android/issues/1310 ViewCompat.setOnApplyWindowInsetsListener( - requireActivity().window.decorView + requireActivity().rootView ) { _, insets -> listView.updatePadding(bottom = insets.safeGetBottomInsets()) insets diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/settings/MainSettingsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/settings/MainSettingsFragment.kt index 2a1d5fd22..52ef392d0 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/settings/MainSettingsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/settings/MainSettingsFragment.kt @@ -28,6 +28,7 @@ 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.hide +import code.name.monkey.retromusic.extensions.rootView import code.name.monkey.retromusic.extensions.safeGetBottomInsets import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.util.NavigationUtil @@ -89,9 +90,9 @@ class MainSettingsFragment : Fragment(), View.OnClickListener { } ViewCompat.setOnApplyWindowInsetsListener( - requireActivity().window.decorView + requireActivity().rootView ) { _, insets -> - binding.container.updatePadding(bottom = insets.safeGetBottomInsets()) + _binding?.container?.updatePadding(bottom = insets.safeGetBottomInsets()) insets } } diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/RetroGlideExtension.kt b/app/src/main/java/code/name/monkey/retromusic/glide/RetroGlideExtension.kt index 9db983669..8b7b83ad9 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/RetroGlideExtension.kt +++ b/app/src/main/java/code/name/monkey/retromusic/glide/RetroGlideExtension.kt @@ -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 getDefaultTransition(): GenericTransitionOptions { return GenericTransitionOptions().transition(DEFAULT_ANIMATION) } +} + +// https://github.com/bumptech/glide/issues/527#issuecomment-148840717 +fun GlideRequest.crossfadeListener(): GlideRequest { + return listener(object : RequestListener { + override fun onLoadFailed( + e: GlideException?, + model: Any?, + target: Target?, + isFirstResource: Boolean + ): Boolean { + return false + } + + override fun onResourceReady( + resource: Drawable?, + model: Any?, + target: Target?, + 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) + } + }) } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverUtils.java b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverUtils.java index 5ffe040cc..5c7fa7aad 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverUtils.java +++ b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverUtils.java @@ -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 diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/BackupHelper.kt b/app/src/main/java/code/name/monkey/retromusic/helper/BackupHelper.kt index aefe5511a..3b1e1cde2 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/BackupHelper.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/BackupHelper.kt @@ -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, backupFile: File) { + private suspend fun zipAll(zipItems: List, 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 { 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 { + 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 { 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 + ) { 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 } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.kt b/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.kt index f6044b2de..c1b6cfee3 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.kt @@ -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() diff --git a/app/src/main/java/code/name/monkey/retromusic/lyrics/CoverLrcView.kt b/app/src/main/java/code/name/monkey/retromusic/lyrics/CoverLrcView.kt new file mode 100644 index 000000000..d722668dc --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/lyrics/CoverLrcView.kt @@ -0,0 +1,711 @@ +/* + * 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 = 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 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 (mOffset != getOffset(0)) { + parent.requestDisallowInterceptTouchEvent(true) + } + if (hasLrc() && mOnPlayClickListener != null) { + 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 (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 setLabel(label: String?) { + runOnUi { + mDefaultLabel = label + invalidate() + } + } + + /** + * 加载歌词文件 + * + * @param lrcFile 歌词文件 + */ + fun loadLrc(lrcFile: File) { + loadLrc(lrcFile, null) + } + + /** + * 加载双语歌词文件,两种语言的歌词时间戳需要一致 + * + * @param mainLrcFile 第一种语言歌词文件 + * @param secondLrcFile 第二种语言歌词文件 + */ + 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>() { + override fun doInBackground(vararg params: File?): List? { + return LrcUtils.parseLrc(params) + } + + override fun onPostExecute(lrcEntries: List) { + 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 第二种语言歌词文本 + */ + 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>() { + override fun doInBackground(vararg params: String?): List? { + return LrcUtils.parseLrc(params) + } + + override fun onPostExecute(lrcEntries: List) { + if (flag === flag) { + onLrcLoaded(lrcEntries) + this@CoverLrcView.flag = null + } + } + }.execute(mainLrcText, secondLrcText) + } + } + /** + * 加载在线歌词 + * + * @param lrcUrl 歌词文件的网络地址 + * @param charset 编码格式 + */ + /** + * 加载在线歌词,默认使用 utf-8 编码 + * + * @param lrcUrl 歌词文件的网络地址 + */ + @JvmOverloads + fun loadLrcByUrl(lrcUrl: String, charset: String? = "utf-8") { + val flag = "url://$lrcUrl" + this.flag = flag + object : AsyncTask() { + 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?) { + 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[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) + } + } + + /** 播放按钮点击监听器,点击后应该跳转到指定播放位置 */ + interface OnPlayClickListener { + /** + * 播放按钮被点击,应该跳转到指定播放位置 + * + * @return 是否成功消费该事件,如果成功消费,则会更新UI + */ + fun onPlayClick(time: Long): Boolean + } + + companion object { + private const val ADJUST_DURATION: Long = 100 + private const val TIMELINE_KEEP_TIME = 4 * DateUtils.SECOND_IN_MILLIS + } + + init { + init(attrs) + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationImpl.kt b/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationImpl.kt index ab4bfb2c7..b897066fe 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationImpl.kt +++ b/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationImpl.kt @@ -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 diff --git a/app/src/main/java/code/name/monkey/retromusic/util/AppRater.kt b/app/src/main/java/code/name/monkey/retromusic/util/AppRater.kt index 64a4302aa..2e74530a0 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/AppRater.kt +++ b/app/src/main/java/code/name/monkey/retromusic/util/AppRater.kt @@ -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 { diff --git a/app/src/main/java/code/name/monkey/retromusic/util/RingtoneManager.kt b/app/src/main/java/code/name/monkey/retromusic/util/RingtoneManager.kt index c7040ccd1..2dd05bded 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/RingtoneManager.kt +++ b/app/src/main/java/code/name/monkey/retromusic/util/RingtoneManager.kt @@ -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( diff --git a/app/src/main/java/code/name/monkey/retromusic/util/ViewUtil.kt b/app/src/main/java/code/name/monkey/retromusic/util/ViewUtil.kt index d582452e5..fdc23c380 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/ViewUtil.kt +++ b/app/src/main/java/code/name/monkey/retromusic/util/ViewUtil.kt @@ -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 { diff --git a/app/src/main/java/code/name/monkey/retromusic/util/theme/ThemeManager.kt b/app/src/main/java/code/name/monkey/retromusic/util/theme/ThemeManager.kt index b95e5ddfc..f03863f61 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/theme/ThemeManager.kt +++ b/app/src/main/java/code/name/monkey/retromusic/util/theme/ThemeManager.kt @@ -29,9 +29,14 @@ object ThemeManager { context: Context ): Int = when (context.generalThemeValue) { LIGHT -> AppCompatDelegate.MODE_NIGHT_NO - DARK, - BLACK -> AppCompatDelegate.MODE_NIGHT_YES - AUTO -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM + DARK -> AppCompatDelegate.MODE_NIGHT_YES + BLACK -> { + if (PreferenceUtil.baseTheme == "dark") { + AppCompatDelegate.MODE_NIGHT_YES + } else { + AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM + } + } + else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM } -} - +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/views/BottomNavigationBarTinted.kt b/app/src/main/java/code/name/monkey/retromusic/views/BottomNavigationBarTinted.kt index 14b96c288..509bb5d15 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/BottomNavigationBarTinted.kt +++ b/app/src/main/java/code/name/monkey/retromusic/views/BottomNavigationBarTinted.kt @@ -16,13 +16,11 @@ package code.name.monkey.retromusic.views import android.content.Context import android.content.res.ColorStateList -import android.graphics.drawable.ColorDrawable import android.util.AttributeSet 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.NavigationViewUtil -import code.name.monkey.retromusic.R import code.name.monkey.retromusic.util.PreferenceUtil import com.google.android.material.bottomnavigation.BottomNavigationView @@ -49,7 +47,6 @@ class BottomNavigationBarTinted @JvmOverloads constructor( accentColor ) itemRippleColor = ColorStateList.valueOf(accentColor.addAlpha(0.08F)) - background = ColorDrawable(ATHUtil.resolveColor(context, R.attr.bottomSheetTint)) itemActiveIndicatorColor = ColorStateList.valueOf(accentColor.addAlpha(0.12F)) } } diff --git a/app/src/main/res/anim/layout_anim_fade.xml b/app/src/main/res/anim/layout_anim_fade.xml new file mode 100644 index 000000000..ba154e91e --- /dev/null +++ b/app/src/main/res/anim/layout_anim_fade.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rounded_drawable.xml b/app/src/main/res/drawable/rounded_drawable.xml new file mode 100644 index 000000000..6d498dbf2 --- /dev/null +++ b/app/src/main/res/drawable/rounded_drawable.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/activity_album_tag_editor.xml b/app/src/main/res/layout-land/activity_album_tag_editor.xml index 43b99d6aa..18b98d1dc 100644 --- a/app/src/main/res/layout-land/activity_album_tag_editor.xml +++ b/app/src/main/res/layout-land/activity_album_tag_editor.xml @@ -3,7 +3,8 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="?attr/colorSurface"> + android:background="?attr/colorSurface" + android:fitsSystemWindows="true"> - + android:layout_height="match_parent"> + app:cardElevation="8dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintDimensionRatio="1:1" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> + android:overScrollMode="@integer/overScrollMode" + app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/imageContainer" + app:layout_constraintTop_toTopOf="parent"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout-land/fragment_album_details.xml b/app/src/main/res/layout-land/fragment_album_details.xml index aa0d1ff66..7e2b90c05 100644 --- a/app/src/main/res/layout-land/fragment_album_details.xml +++ b/app/src/main/res/layout-land/fragment_album_details.xml @@ -55,6 +55,7 @@ android:descendantFocusability="beforeDescendants" android:fillViewport="true" android:focusableInTouchMode="true" + android:overScrollMode="@integer/overScrollMode" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" diff --git a/app/src/main/res/layout-land/fragment_artist_details.xml b/app/src/main/res/layout-land/fragment_artist_details.xml index 82d7614de..50c9c1882 100644 --- a/app/src/main/res/layout-land/fragment_artist_details.xml +++ b/app/src/main/res/layout-land/fragment_artist_details.xml @@ -55,6 +55,7 @@ android:layout_weight="1" android:descendantFocusability="beforeDescendants" android:focusableInTouchMode="true" + android:overScrollMode="@integer/overScrollMode" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" diff --git a/app/src/main/res/layout-land/fragment_banner_home.xml b/app/src/main/res/layout-land/fragment_banner_home.xml index 6b8571a43..858d33922 100644 --- a/app/src/main/res/layout-land/fragment_banner_home.xml +++ b/app/src/main/res/layout-land/fragment_banner_home.xml @@ -63,6 +63,7 @@ android:layout_marginEnd="@dimen/toolbar_margin_horizontal" android:descendantFocusability="beforeDescendants" android:focusableInTouchMode="true" + android:overScrollMode="@integer/overScrollMode" android:transitionGroup="true" app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> diff --git a/app/src/main/res/layout-land/fragment_circle_player.xml b/app/src/main/res/layout-land/fragment_circle_player.xml index 07d734c6c..cff4fcf3c 100644 --- a/app/src/main/res/layout-land/fragment_circle_player.xml +++ b/app/src/main/res/layout-land/fragment_circle_player.xml @@ -54,29 +54,46 @@ app:navigationIcon="@drawable/ic_keyboard_arrow_down_black" tools:layout_editor_absoluteY="24dp" /> - - + + + + + + + diff --git a/app/src/main/res/layout-sw600dp/fragment_playing_queue.xml b/app/src/main/res/layout-sw600dp/fragment_playing_queue.xml index bdd18ae3c..7dae61e77 100644 --- a/app/src/main/res/layout-sw600dp/fragment_playing_queue.xml +++ b/app/src/main/res/layout-sw600dp/fragment_playing_queue.xml @@ -42,6 +42,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:clipToPadding="false" + android:overScrollMode="@integer/overScrollMode" android:paddingBottom="96dp" android:scrollbars="none" app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" diff --git a/app/src/main/res/layout/abs_playlists.xml b/app/src/main/res/layout/abs_playlists.xml index bf4b5e80c..ee11757cd 100644 --- a/app/src/main/res/layout/abs_playlists.xml +++ b/app/src/main/res/layout/abs_playlists.xml @@ -43,7 +43,6 @@ android:layout_marginEnd="16dp" android:text="@string/my_top_tracks" app:icon="@drawable/ic_trending_up" - app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toStartOf="@+id/actionShuffle" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="@+id/history" diff --git a/app/src/main/res/layout/activity_album_tag_editor.xml b/app/src/main/res/layout/activity_album_tag_editor.xml index 5a40e8ee1..e3c4e3745 100755 --- a/app/src/main/res/layout/activity_album_tag_editor.xml +++ b/app/src/main/res/layout/activity_album_tag_editor.xml @@ -26,7 +26,8 @@ android:id="@+id/content" android:layout_width="match_parent" android:layout_height="wrap_content" - app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> + app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" + android:overScrollMode="@integer/overScrollMode"> - - + android:background="?attr/colorSurface" + android:fitsSystemWindows="true"> + android:layout_height="wrap_content" + android:scrollbars="none" + android:fitsSystemWindows="true" + app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" /> diff --git a/app/src/main/res/layout/activity_restore.xml b/app/src/main/res/layout/activity_restore.xml index 79eda3368..5f5224af6 100644 --- a/app/src/main/res/layout/activity_restore.xml +++ b/app/src/main/res/layout/activity_restore.xml @@ -1,8 +1,84 @@ - + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="16dp"> - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_song_tag_editor.xml b/app/src/main/res/layout/activity_song_tag_editor.xml index d054429c5..dacd1ce84 100755 --- a/app/src/main/res/layout/activity_song_tag_editor.xml +++ b/app/src/main/res/layout/activity_song_tag_editor.xml @@ -1,5 +1,6 @@ + + + + + + - - - + android:baselineAligned="false" + android:orientation="horizontal"> - - + android:layout_marginTop="8dp" + android:layout_weight="1" + app:hintEnabled="true"> - + + + + + + + + + + + - - - + + + + + + + + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/volumeSeekBar" /> + android:overScrollMode="@integer/overScrollMode" + tools:listitem="@layout/item_list" /> diff --git a/app/src/main/res/layout/fragment_flat_player_playback_controls.xml b/app/src/main/res/layout/fragment_flat_player_playback_controls.xml index 283b1868e..bee59f8f8 100644 --- a/app/src/main/res/layout/fragment_flat_player_playback_controls.xml +++ b/app/src/main/res/layout/fragment_flat_player_playback_controls.xml @@ -153,7 +153,6 @@ android:layout_height="52dp" android:layout_centerVertical="true" android:background="?colorAccent" - android:foreground="?attr/rectSelector" android:padding="12dp" android:scaleType="fitCenter" tools:ignore="MissingPrefix" diff --git a/app/src/main/res/layout/fragment_folder.xml b/app/src/main/res/layout/fragment_folder.xml index 56b086604..6521a4a0a 100644 --- a/app/src/main/res/layout/fragment_folder.xml +++ b/app/src/main/res/layout/fragment_folder.xml @@ -82,6 +82,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:clipToPadding="false" + android:overScrollMode="@integer/overScrollMode" android:scrollbars="none" app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_gradient_player.xml b/app/src/main/res/layout/fragment_gradient_player.xml index ea2e8b1e5..e9aad0649 100644 --- a/app/src/main/res/layout/fragment_gradient_player.xml +++ b/app/src/main/res/layout/fragment_gradient_player.xml @@ -150,6 +150,7 @@ android:layout_width="0dp" android:layout_height="0dp" android:background="?attr/colorSurface" + android:overScrollMode="@integer/overScrollMode" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 618ed3995..1c8cf07d8 100755 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -61,6 +61,7 @@ android:layout_height="wrap_content" android:descendantFocusability="beforeDescendants" android:focusableInTouchMode="true" + android:overScrollMode="@integer/overScrollMode" app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> + android:overScrollMode="@integer/overScrollMode" + android:transitionGroup="true" /> + android:layout_height="wrap_content" + android:overScrollMode="@integer/overScrollMode"> @@ -7,49 +8,24 @@ + android:layout_height="match_parent" + android:overScrollMode="@integer/overScrollMode"> - + + + app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" + app:lrcLabel="@string/no_lyrics_found" + app:lrcNormalTextSize="28sp" + app:lrcPadding="24dp" + app:lrcTextGravity="center" + app:lrcTextSize="32sp" + app:lrcTimelineColor="@color/transparent" + tools:visibility="visible" /> - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_playing_queue.xml b/app/src/main/res/layout/fragment_playing_queue.xml index 9fa45c55e..03084d6ba 100644 --- a/app/src/main/res/layout/fragment_playing_queue.xml +++ b/app/src/main/res/layout/fragment_playing_queue.xml @@ -31,6 +31,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:clipToPadding="false" + android:overScrollMode="@integer/overScrollMode" android:paddingBottom="96dp" android:scrollbars="none" app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" diff --git a/app/src/main/res/layout/fragment_playlist_detail.xml b/app/src/main/res/layout/fragment_playlist_detail.xml index c5b1e93ab..ffaf5985d 100644 --- a/app/src/main/res/layout/fragment_playlist_detail.xml +++ b/app/src/main/res/layout/fragment_playlist_detail.xml @@ -35,7 +35,9 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:clipToPadding="false" + android:overScrollMode="@integer/overScrollMode" android:scrollbars="none" + android:layoutAnimation="@anim/layout_anim_fade" app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" /> + android:scrollbars="none" + android:overScrollMode="@integer/overScrollMode"> diff --git a/app/src/main/res/layout/home_content.xml b/app/src/main/res/layout/home_content.xml index 10f38720b..133954bcf 100644 --- a/app/src/main/res/layout/home_content.xml +++ b/app/src/main/res/layout/home_content.xml @@ -66,7 +66,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:nestedScrollingEnabled="false" - tools:itemCount="10" + android:overScrollMode="@integer/overScrollMode" + tools:itemCount="10" tools:layoutManager="androidx.recyclerview.widget.GridLayoutManager" tools:listitem="@layout/item_album_card" tools:spanCount="3" /> diff --git a/app/src/main/res/layout/mcab_toolbar.xml b/app/src/main/res/layout/mcab_toolbar.xml index 9efe854e6..0ad645b06 100644 --- a/app/src/main/res/layout/mcab_toolbar.xml +++ b/app/src/main/res/layout/mcab_toolbar.xml @@ -4,5 +4,5 @@ android:layout_width="match_parent" android:layout_height="?actionBarSize" android:elevation="@dimen/mcab_toolbar_elevation" - android:theme="@style/ThemeOverlay.Material3.ActionBar" + android:theme="@style/mcab_theme" tools:ignore="UnusedAttribute" /> \ No newline at end of file diff --git a/app/src/main/res/layout/section_recycler_view.xml b/app/src/main/res/layout/section_recycler_view.xml index f554b90dd..5840f9afc 100644 --- a/app/src/main/res/layout/section_recycler_view.xml +++ b/app/src/main/res/layout/section_recycler_view.xml @@ -41,6 +41,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:nestedScrollingEnabled="false" + android:overScrollMode="@integer/overScrollMode" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/clickable_area" diff --git a/app/src/main/res/values-night-v27/styles.xml b/app/src/main/res/values-night-v27/styles.xml index 4666e03d8..17d658a0a 100644 --- a/app/src/main/res/values-night-v27/styles.xml +++ b/app/src/main/res/values-night-v27/styles.xml @@ -29,7 +29,6 @@ false @color/darkColorSurface @style/Widget.MaterialComponents.CardView - @color/bottomSheetColor @color/elevationOverlayDark @style/Widget.MaterialComponents.FloatingActionButton diff --git a/app/src/main/res/values-v27/styles_parents.xml b/app/src/main/res/values-v27/styles_parents.xml index 2aea8a247..c80f0d996 100644 --- a/app/src/main/res/values-v27/styles_parents.xml +++ b/app/src/main/res/values-v27/styles_parents.xml @@ -27,7 +27,6 @@ @style/Widget.Material3.BottomNavigationView @style/MaterialButtonTheme @style/Widget.Material3.CardView.Elevated - @color/bottomSheetColor @color/elevationOverlay @style/Widget.MaterialComponents.FloatingActionButton @@ -54,7 +53,6 @@ @style/Widget.Material3.BottomNavigationView @color/window_color_dark @style/Widget.Material3.CardView.Elevated - @color/bottomSheetColor @color/elevationOverlay @style/Widget.MaterialComponents.FloatingActionButton @@ -78,7 +76,6 @@ @style/MaterialButtonTheme @style/Widget.Material3.BottomNavigationView @style/Widget.Material3.CardView.Elevated - @color/bottomSheetColorLight @color/elevationOverlayLight @style/Widget.MaterialComponents.FloatingActionButton diff --git a/app/src/main/res/values-v31/donottranslate.xml b/app/src/main/res/values-v31/donottranslate.xml index f4981725e..f39fb68c1 100644 --- a/app/src/main/res/values-v31/donottranslate.xml +++ b/app/src/main/res/values-v31/donottranslate.xml @@ -4,4 +4,5 @@ true false + 0 \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 63e3c4278..d2db61cfb 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -6,7 +6,6 @@ - diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index eff1e1c7d..893f869f9 100755 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -28,4 +28,6 @@ false true + 2 + true \ No newline at end of file diff --git a/app/src/main/res/values/lrc_dimens.xml b/app/src/main/res/values/lrc_dimens.xml index 4a5c994f4..b022d846b 100644 --- a/app/src/main/res/values/lrc_dimens.xml +++ b/app/src/main/res/values/lrc_dimens.xml @@ -1,8 +1,8 @@ 1000 - 16sp - 12sp + 20sp + 16sp 16dp 1dp 30dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 229596627..862ba952f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -73,6 +73,7 @@ Full Image Card Classic + MD3 Small Minimal Text Artist @@ -81,6 +82,7 @@ Audio focus denied. Change the sound settings and adjust the equalizer controls Auto + Backup and restore your settings, playlists @@ -113,6 +115,7 @@ Cascading Changelog Check out What\'s New + Choose what to restore Circle Circular Classic @@ -132,7 +135,9 @@ Created playlist %1$s. Members and contributors Currently listening to %1$s by %2$s. + Custom Artist Images Kinda Dark + Databases (Playlists, History, Most Played, etc.) Delete playlist %1$s?]]> @@ -156,6 +161,7 @@ Device info Allow Retro Music to modify audio settings Set ringtone + Disc Number Do you want to clear the blacklist? %1$s from the blacklist?]]> @@ -404,6 +410,7 @@ Reset Reset artist image Restore + Do you want to restore backup? Restored previous purchase. Please restart the app to make use of all features. Restored previous purchases. Restoring purchase… @@ -453,8 +460,8 @@ Sort order Ascending Album - Artist @string/album_artist + Artist Composer Date added Date modified @@ -480,6 +487,7 @@ Tiny Tiny card Title + New Backup Today Top albums Top artists @@ -495,6 +503,7 @@ Up next Update image Updating… + User Images User Name Username Version @@ -515,8 +524,4 @@ You have to select at least one category. You will be forwarded to the issue tracker website. Your account data is only used for authentication. - Do you want to restore backup? - New Backup - Backup and restore your settings, playlists - MD3 diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index d1fbc9261..e8e081dff 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,19 +1,10 @@ - + + + + + + + diff --git a/app/src/main/res/values/styles_parents.xml b/app/src/main/res/values/styles_parents.xml index 001b26ee6..6229dedbb 100644 --- a/app/src/main/res/values/styles_parents.xml +++ b/app/src/main/res/values/styles_parents.xml @@ -14,8 +14,7 @@ @style/MaterialButtonTheme @style/MaterialPopupMenuStyle @style/MaterialPopupMenuStyle - @style/Widget.Material3.CardView.Elevated - @color/bottomSheetColor + @style/Widget.Material3.CardView.ElevatedT @color/elevationOverlay @style/Widget.MaterialComponents.FloatingActionButton @@ -40,7 +39,6 @@ @color/window_color_dark @style/MaterialPopupMenuStyle @style/Widget.Material3.CardView.Elevated - @color/bottomSheetColor @color/elevationOverlay @style/Widget.MaterialComponents.FloatingActionButton @@ -63,7 +61,6 @@ @style/ThemeOverlay.AppCompat.Dark @style/MaterialPopupMenuStyle @style/Widget.Material3.CardView.Elevated - @color/bottomSheetColorBlack @color/elevationOverlayDark @style/Widget.MaterialComponents.FloatingActionButton @@ -86,7 +83,6 @@ @style/MaterialButtonTheme @style/MaterialPopupMenuStyle @style/Widget.Material3.CardView.Elevated - @color/bottomSheetColorLight @color/elevationOverlayLight @style/Widget.MaterialComponents.FloatingActionButton diff --git a/appthemehelper/build.gradle b/appthemehelper/build.gradle index 407e90903..383b4637d 100644 --- a/appthemehelper/build.gradle +++ b/appthemehelper/build.gradle @@ -10,7 +10,7 @@ android { buildTypes { release { minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { @@ -20,10 +20,9 @@ android { } dependencies { - implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'androidx.appcompat:appcompat:1.4.0' - implementation 'com.google.android.material:material:1.5.0-beta01' - implementation 'androidx.preference:preference-ktx:1.2.0-beta01' + implementation "com.google.android.material:material:$mdc_version" + implementation "androidx.preference:preference-ktx:$preference_version" implementation 'androidx.cardview:cardview:1.0.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/ATH.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/ATH.kt index 73644f888..904117d4d 100755 --- a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/ATH.kt +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/ATH.kt @@ -3,7 +3,6 @@ package code.name.monkey.appthemehelper import android.app.Activity import android.app.ActivityManager import android.content.Context -import android.graphics.Color import android.os.Build import android.view.View import androidx.annotation.ColorInt diff --git a/build.gradle b/build.gradle index 42064114f..764e4911b 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,12 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.6.0' + ext.kotlin_version = '1.6.10' + ext.navigation_version = '2.4.0-rc01' + ext.mdc_version = '1.5.0-rc01' + ext.preference_version = '1.2.0-rc01' + + repositories { mavenCentral() google() @@ -9,11 +14,12 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:7.0.4' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - def nav_version = "2.4.0-beta02" - classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" + classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version" } } - +plugins { + id "com.github.ben-manes.versions" version "0.39.0" +} task clean(type: Delete) { delete rootProject.buildDir } \ No newline at end of file