This commit is contained in:
Hemanth S 2021-12-20 17:14:45 +05:30
commit 80739f412a
105 changed files with 2235 additions and 625 deletions

View file

@ -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'
}

View file

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

View file

@ -22,7 +22,7 @@
<application
android:name=".App"
android:allowBackup="true"
android:allowBackup="@bool/allowBackup"
android:configChanges="locale|layoutDirection"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
@ -123,15 +123,38 @@
<activity android:name=".activities.LockScreenActivity" />
<activity
android:name=".fragments.backup.RestoreActivity"
android:exported="true">
android:excludeFromRecents="false"
android:exported="true"
android:label="@string/restore"
android:theme="@style/Theme.RetroMusic.Dialog">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:mimeType="application/octet-stream" />
<data android:mimeType="application/x-zip-compressed" />
<data android:mimeType="application/zip" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:mimeType="*/*" />
<!--
Work around Android's ugly primitive PatternMatcher
implementation that can't cope with finding a . early in
the path unless it's explicitly matched.
-->
<data android:host="*" />
<data android:pathPattern=".*\\.rmbak" />
<data android:pathPattern=".*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.rmbak" />
</intent-filter>
</activity>
@ -273,10 +296,9 @@
<service
android:name=".service.MusicService"
android:enabled="true"
android:exported="true"
android:exported="false"
android:foregroundServiceType="mediaPlayback"
android:label="@string/app_name"
tools:ignore="ExportedService">
android:label="@string/app_name">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>

View file

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

View file

@ -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
/**

View file

@ -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<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
ToolbarContentTintHelper.colorBackButton(toolbar)
toolbar.setBackgroundColor(resolveColor(this, R.attr.colorSurface))
val webView = findViewById<WebView>(R.id.license)
binding = ActivityLicenseBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.toolbar)
ToolbarContentTintHelper.colorBackButton(binding.toolbar)
try {
val buf = StringBuilder()
val json = assets.open("oldindex.html")
val br = BufferedReader(InputStreamReader(json, StandardCharsets.UTF_8))
var str: String?
while (br.readLine().also { str = it } != null) {
buf.append(str)
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(
"<h1>Unable to load</h1><p>" + e.localizedMessage + "</p>", "text/html", "UTF-8"
)
}
binding.license.drawAboveSystemBars()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {

View file

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

View file

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

View file

@ -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 {

View file

@ -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)

View file

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

View file

@ -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<ActivityAlbumTagEditorBindin
window.enterTransition = slide
}
override fun loadImageFromFile(selectedFile: Uri?) {
GlideApp.with(this@AlbumTagEditorActivity).asBitmapPalette().load(selectedFile)
.diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true)
.into(object : ImageViewTarget<BitmapPaletteWrapper>(binding.editorImage) {
override fun onResourceReady(
resource: BitmapPaletteWrapper,
transition: Transition<in BitmapPaletteWrapper>?
) {
getColor(resource.palette, Color.TRANSPARENT)
albumArtBitmap = resource.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) }
setImageBitmap(
albumArtBitmap,
getColor(
resource.palette,
ATHUtil.resolveColor(
this@AlbumTagEditorActivity,
R.attr.defaultFooterColor
)
)
)
deleteAlbumArt = false
dataChanged()
setResult(Activity.RESULT_OK)
}
override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(errorDrawable)
Toast.makeText(this@AlbumTagEditorActivity, "Load Failed", Toast.LENGTH_LONG)
.show()
}
override fun setResource(resource: BitmapPaletteWrapper?) {}
})
}
private var albumArtBitmap: Bitmap? = null
private var deleteAlbumArt: Boolean = false
@ -171,6 +138,41 @@ class AlbumTagEditorActivity : AbsTagEditorActivity<ActivityAlbumTagEditorBindin
dataChanged()
}
override fun loadImageFromFile(selectedFile: Uri?) {
GlideApp.with(this@AlbumTagEditorActivity).asBitmapPalette().load(selectedFile)
.diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true)
.into(object : ImageViewTarget<BitmapPaletteWrapper>(binding.editorImage) {
override fun onResourceReady(
resource: BitmapPaletteWrapper,
transition: Transition<in BitmapPaletteWrapper>?
) {
getColor(resource.palette, Color.TRANSPARENT)
albumArtBitmap = resource.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) }
setImageBitmap(
albumArtBitmap,
getColor(
resource.palette,
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, String>(FieldKey::class.java)
fieldKeyValueMap[FieldKey.ALBUM] = binding.albumText.text.toString()
@ -213,6 +215,16 @@ class AlbumTagEditorActivity : AbsTagEditorActivity<ActivityAlbumTagEditorBindin
override fun setColors(color: Int) {
super.setColors(color)
saveFab.backgroundTintList = ColorStateList.valueOf(color)
saveFab.backgroundTintList = ColorStateList.valueOf(color)
ColorStateList.valueOf(
MaterialValueHelper.getPrimaryTextColor(
this,
color.isColorLight
)
).also {
saveFab.iconTint = it
saveFab.setTextColor(it)
}
}

View file

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

View file

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

View file

@ -33,9 +33,10 @@ import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.menu.SongMenuHelper
import code.name.monkey.retromusic.model.*
import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist
import code.name.monkey.retromusic.repository.PlaylistSongsLoader
import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.model.Genre
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil
import java.util.*

View file

@ -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

View file

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

View file

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

View file

@ -9,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<View>(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<View>(R.id.status_bar)
if (statusBar != null) {
statusBar.setBackgroundColor(
ColorUtil.darkenColor(
color
)
)
} else {
window.statusBarColor = ColorUtil.darkenColor(color)
}
}

View file

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

View file

@ -66,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()
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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

View file

@ -27,12 +27,10 @@ import androidx.recyclerview.widget.LinearLayoutManager
import code.name.monkey.retromusic.EXTRA_GENRE
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.GenreAdapter
import code.name.monkey.retromusic.extensions.navigate
import code.name.monkey.retromusic.fragments.ReloadType
import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewFragment
import code.name.monkey.retromusic.interfaces.IGenreClickListener
import code.name.monkey.retromusic.model.Genre
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil
import com.google.android.gms.cast.framework.CastButtonFactory
import com.google.android.material.transition.MaterialSharedAxis

View file

@ -23,6 +23,7 @@ import android.view.View
import androidx.activity.addCallback
import androidx.core.os.bundleOf
import androidx.core.text.HtmlCompat
import androidx.core.view.doOnLayout
import androidx.core.view.doOnPreDraw
import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.navigation.fragment.findNavController
@ -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() {

View file

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

View file

@ -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)

View file

@ -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) {

View file

@ -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() {

View file

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

View file

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

View file

@ -18,19 +18,19 @@ import android.animation.ObjectAnimator
import android.content.Context
import android.graphics.Color
import android.graphics.PorterDuff
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.media.AudioManager
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.Animation
import android.view.animation.LinearInterpolator
import android.widget.SeekBar
import androidx.appcompat.widget.Toolbar
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.appthemehelper.util.*
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentCirclePlayerBinding
import code.name.monkey.retromusic.extensions.*
@ -38,6 +38,7 @@ import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.base.goToAlbum
import code.name.monkey.retromusic.fragments.base.goToArtist
import code.name.monkey.retromusic.glide.*
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper.Callback
@ -47,10 +48,9 @@ import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.ViewUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import code.name.monkey.retromusic.views.SeekArc
import code.name.monkey.retromusic.views.SeekArc.OnSeekArcChangeListener
import code.name.monkey.retromusic.volume.AudioVolumeObserver
import code.name.monkey.retromusic.volume.OnAudioVolumeChangedListener
import me.tankery.lib.circularseekbar.CircularSeekBar
/**
* Created by hemanths on 2020-01-06.
@ -58,7 +58,7 @@ import code.name.monkey.retromusic.volume.OnAudioVolumeChangedListener
class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player), Callback,
OnAudioVolumeChangedListener,
OnSeekArcChangeListener {
CircularSeekBar.OnCircularSeekBarChangeListener {
private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper
private var audioVolumeObserver: AudioVolumeObserver? = null
@ -69,6 +69,9 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
private var _binding: FragmentCirclePlayerBinding? = null
private val binding get() = _binding!!
private var rotateAnimator: ObjectAnimator? = null
private var lastRequest: GlideRequest<Drawable>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
progressViewUpdateHelper = MusicProgressViewUpdateHelper(this)
@ -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() {

View file

@ -26,14 +26,13 @@ import android.view.MenuItem
import android.view.View
import android.view.animation.DecelerateInterpolator
import android.view.animation.LinearInterpolator
import 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

View file

@ -23,9 +23,9 @@ import android.graphics.drawable.AnimatedVectorDrawable
import android.os.Bundle
import android.view.View
import android.view.animation.LinearInterpolator
import 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

View file

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

View file

@ -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()

View file

@ -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

View file

@ -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
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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<LrcEntry> = ArrayList()
private val mLrcPaint = TextPaint()
private val mTimePaint = TextPaint()
private var mTimeFontMetrics: Paint.FontMetrics? = null
private var mPlayDrawable: Drawable? = null
private var mDividerHeight = 0f
private var mAnimationDuration: Long = 0
private var mNormalTextColor = 0
private var mNormalTextSize = 0f
private var mCurrentTextColor = 0
private var mCurrentTextSize = 0f
private var mTimelineTextColor = 0
private var mTimelineColor = 0
private var mTimeTextColor = 0
private var mDrawableWidth = 0
private var mTimeTextWidth = 0
private var mDefaultLabel: String? = null
private var mLrcPadding = 0f
private var mOnPlayClickListener: OnPlayClickListener? = null
private var 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<File?, Int?, List<LrcEntry>>() {
override fun doInBackground(vararg params: File?): List<LrcEntry>? {
return LrcUtils.parseLrc(params)
}
override fun onPostExecute(lrcEntries: List<LrcEntry>) {
if (flag === flag) {
onLrcLoaded(lrcEntries)
this@CoverLrcView.flag = null
}
}
}.execute(mainLrcFile, secondLrcFile)
}
}
/**
* 加载歌词文本
*
* @param lrcText 歌词文本
*/
fun loadLrc(lrcText: String?) {
loadLrc(lrcText, null)
}
/**
* 加载双语歌词文本两种语言的歌词时间戳需要一致
*
* @param mainLrcText 第一种语言歌词文本
* @param secondLrcText 第二种语言歌词文本
*/
fun loadLrc(mainLrcText: String?, secondLrcText: String?) {
runOnUi {
reset()
val sb = StringBuilder("file://")
sb.append(mainLrcText)
if (secondLrcText != null) {
sb.append("#").append(secondLrcText)
}
val flag = sb.toString()
this.flag = flag
object : AsyncTask<String?, Int?, List<LrcEntry>>() {
override fun doInBackground(vararg params: String?): List<LrcEntry>? {
return LrcUtils.parseLrc(params)
}
override fun onPostExecute(lrcEntries: List<LrcEntry>) {
if (flag === flag) {
onLrcLoaded(lrcEntries)
this@CoverLrcView.flag = null
}
}
}.execute(mainLrcText, secondLrcText)
}
}
/**
* 加载在线歌词
*
* @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<String?, Int?, String>() {
override fun doInBackground(vararg params: String?): String? {
return LrcUtils.getContentFromNetwork(params[0], params[1])
}
override fun onPostExecute(lrcText: String) {
if (flag === flag) {
loadLrc(lrcText)
}
}
}.execute(lrcUrl, charset)
}
/**
* 歌词是否有效
*
* @return true如果歌词有效否则false
*/
fun hasLrc(): Boolean {
return mLrcEntryList.isNotEmpty()
}
/**
* 刷新歌词
*
* @param time 当前播放时间
*/
fun updateTime(time: Long) {
runOnUi {
if (!hasLrc()) {
return@runOnUi
}
val line = findShowLine(time)
if (line != mCurrentLine) {
mCurrentLine = line
if (!isShowTimeline) {
smoothScrollTo(line)
} else {
invalidate()
}
}
}
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
if (changed) {
initPlayDrawable()
initEntryList()
if (hasLrc()) {
smoothScrollTo(mCurrentLine, 0L)
}
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val centerY = height / 2
// 无歌词文件
if (!hasLrc()) {
mLrcPaint.color = mCurrentTextColor
@SuppressLint("DrawAllocation") val staticLayout = StaticLayout(
mDefaultLabel,
mLrcPaint,
lrcWidth.toInt(),
Layout.Alignment.ALIGN_CENTER,
1f,
0f,
false
)
drawText(canvas, staticLayout, centerY.toFloat())
return
}
val centerLine = centerLine
if (isShowTimeline) {
mPlayDrawable?.draw(canvas)
mTimePaint.color = mTimeTextColor
val timeText = LrcUtils.formatTime(mLrcEntryList[centerLine].time)
val timeX = (width - mTimeTextWidth / 2).toFloat()
val timeY = centerY - (mTimeFontMetrics!!.descent + mTimeFontMetrics!!.ascent) / 2
canvas.drawText(timeText, timeX, timeY, mTimePaint)
}
canvas.translate(0f, mOffset)
var y = 0f
for (i in mLrcEntryList.indices) {
if (i > 0) {
y += ((mLrcEntryList[i - 1].height + mLrcEntryList[i].height shr 1)
+ mDividerHeight)
}
if (i == mCurrentLine) {
mLrcPaint.textSize = mCurrentTextSize
mLrcPaint.color = mCurrentTextColor
} else if (isShowTimeline && i == centerLine) {
mLrcPaint.color = mTimelineTextColor
} else {
mLrcPaint.textSize = mNormalTextSize
mLrcPaint.color = mNormalTextColor
}
drawText(canvas, mLrcEntryList[i].staticLayout, y)
}
}
/**
* 画一行歌词
*
* @param y 歌词中心 Y 坐标
*/
private fun drawText(canvas: Canvas, staticLayout: StaticLayout, y: Float) {
canvas.save()
canvas.translate(mLrcPadding, y - (staticLayout.height shr 1))
staticLayout.draw(canvas)
canvas.restore()
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_UP
|| event.action == MotionEvent.ACTION_CANCEL
) {
isTouching = false
if (hasLrc() && !isFling) {
adjustCenter()
postDelayed(hideTimelineRunnable, TIMELINE_KEEP_TIME)
}
}
return mGestureDetector!!.onTouchEvent(event)
}
override fun computeScroll() {
if (mScroller!!.computeScrollOffset()) {
mOffset = mScroller!!.currY.toFloat()
invalidate()
}
if (isFling && mScroller!!.isFinished) {
isFling = false
if (hasLrc() && !isTouching) {
adjustCenter()
postDelayed(hideTimelineRunnable, TIMELINE_KEEP_TIME)
}
}
}
override fun onDetachedFromWindow() {
removeCallbacks(hideTimelineRunnable)
super.onDetachedFromWindow()
}
private fun onLrcLoaded(entryList: List<LrcEntry>?) {
if (entryList != null && entryList.isNotEmpty()) {
mLrcEntryList.addAll(entryList)
}
mLrcEntryList.sort()
initEntryList()
invalidate()
}
private fun initPlayDrawable() {
val l = (mTimeTextWidth - mDrawableWidth) / 2
val t = height / 2 - mDrawableWidth / 2
val r = l + mDrawableWidth
val b = t + mDrawableWidth
mPlayDrawable!!.setBounds(l, t, r, b)
}
private fun initEntryList() {
if (!hasLrc() || width == 0) {
return
}
for (lrcEntry in mLrcEntryList) {
lrcEntry.init(mLrcPaint, lrcWidth.toInt(), mTextGravity)
}
mOffset = (height / 2).toFloat()
}
fun reset() {
endAnimation()
mScroller!!.forceFinished(true)
isShowTimeline = false
isTouching = false
isFling = false
removeCallbacks(hideTimelineRunnable)
mLrcEntryList.clear()
mOffset = 0f
mCurrentLine = 0
invalidate()
}
/** 将中心行微调至正中心 */
private fun adjustCenter() {
smoothScrollTo(centerLine, ADJUST_DURATION)
}
/** 滚动到某一行 */
/** 滚动到某一行 */
private fun smoothScrollTo(line: Int, duration: Long = mAnimationDuration) {
val offset = getOffset(line)
endAnimation()
mAnimator = ValueAnimator.ofFloat(mOffset, offset).apply {
this.duration = duration
interpolator = LinearInterpolator()
addUpdateListener { animation: ValueAnimator ->
mOffset = animation.animatedValue as Float
invalidate()
}
LrcUtils.resetDurationScale()
start()
}
}
/** 结束滚动动画 */
private fun endAnimation() {
if (mAnimator != null && mAnimator!!.isRunning) {
mAnimator!!.end()
}
}
/** 二分法查找当前时间应该显示的行数(最后一个 <= time 的行数) */
private fun findShowLine(time: Long): Int {
var left = 0
var right = mLrcEntryList.size
while (left <= right) {
val middle = (left + right) / 2
val middleTime = mLrcEntryList[middle].time
if (time < middleTime) {
right = middle - 1
} else {
if (middle + 1 >= mLrcEntryList.size || time < mLrcEntryList[middle + 1].time) {
return middle
}
left = middle + 1
}
}
return 0
}
/** 获取当前在视图中央的行数 */
private val centerLine: Int
get() {
var centerLine = 0
var minDistance = Float.MAX_VALUE
for (i in mLrcEntryList.indices) {
if (abs(mOffset - getOffset(i)) < minDistance) {
minDistance = abs(mOffset - getOffset(i))
centerLine = i
}
}
return centerLine
}
/** 获取歌词距离视图顶部的距离 采用懒加载方式 */
private fun getOffset(line: Int): Float {
if (mLrcEntryList[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)
}
}

View file

@ -30,8 +30,6 @@ import androidx.media.app.NotificationCompat.MediaStyle
import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.db.PlaylistEntity
import code.name.monkey.retromusic.db.toSongEntity
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper

View file

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

View file

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

View file

@ -26,7 +26,6 @@ import androidx.core.graphics.BlendModeCompat.SRC_IN
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import com.google.android.material.progressindicator.CircularProgressIndicator
object ViewUtil {

View file

@ -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
}
}
}

View file

@ -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))
}
}

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
android:animation="@android:anim/fade_in"
android:animationOrder="normal"
android:delay="15%" />

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?colorSurface"/>
<corners android:radius="28dp" />
</shape>

View file

@ -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">
<LinearLayout
android:layout_width="match_parent"
@ -16,20 +17,22 @@
app:navigationIcon="@drawable/ic_keyboard_backspace_black"
app:title="@string/action_tag_editor" />
<LinearLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
android:layout_height="match_parent">
<com.google.android.material.card.MaterialCardView
android:id="@+id/imageContainer"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="16dp"
android:layout_weight="1"
android:transitionName="@string/transition_album_art"
app:cardCornerRadius="24dp"
app:cardElevation="8dp">
app:cardElevation="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/editorImage"
@ -50,10 +53,14 @@
<androidx.core.widget.NestedScrollView
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
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">
<LinearLayout
android:layout_width="match_parent"
@ -137,7 +144,7 @@
android:layout_height="52dp" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
<com.google.android.material.button.MaterialButton

View file

@ -0,0 +1,321 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:fitsSystemWindows="true"
android:focusable="true"
android:focusableInTouchMode="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="false"
android:orientation="vertical">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
style="@style/Toolbar"
app:navigationIcon="@drawable/ic_keyboard_backspace_black"
app:title="@string/action_tag_editor" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.card.MaterialCardView
android:id="@+id/imageContainer"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_gravity="center"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="40dp"
android:transitionName="@string/transition_album_art"
app:cardCornerRadius="@dimen/m3_card_large_radius"
app:cardElevation="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/editorImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:srcCompat="@tools:sample/backgrounds/scenic[5]" />
</com.google.android.material.card.MaterialCardView>
<androidx.core.widget.NestedScrollView
android:id="@+id/content"
android:layout_width="0dp"
android:layout_height="0dp"
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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/songTextContainer"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:hintEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/songText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/song"
android:inputType="text|textCapWords"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/albumTextContainer"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:hintEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/albumText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/album"
android:inputType="text|textCapWords"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/artistContainer"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:hintEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/artistText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_vertical"
android:hint="@string/artist"
android:inputType="text|textCapWords"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/albumArtistContainer"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:hintEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/albumArtistText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_vertical"
android:hint="@string/album_artist"
android:inputType="text|textCapWords"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/composerContainer"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:hintEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/songComposerText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_vertical"
android:hint="@string/composer"
android:inputType="text|textCapWords"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:baselineAligned="false"
android:orientation="horizontal">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/genreContainer"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_weight="1"
app:hintEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/genreText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_vertical"
android:hint="@string/genre"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/yearContainer"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_weight="1"
app:hintEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/yearText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_vertical"
android:hint="@string/year"
android:inputType="text|number"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:baselineAligned="false"
android:orientation="horizontal">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/trackNumberContainer"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_weight="1"
app:hintEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/trackNumberText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_vertical"
android:hint="@string/track_hint"
android:inputType="text|number"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/discNumberContainer"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_weight="1"
app:hintEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/discNumberText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_vertical"
android:hint="@string/disc_hint"
android:inputType="text|number"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/lyricsContainer"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:hintEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/lyricsText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_vertical"
android:hint="@string/lyrics"
android:inputType="textMultiLine"
android:maxLines="15" />
</com.google.android.material.textfield.TextInputLayout>
<Space
android:layout_width="match_parent"
android:layout_height="52dp" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/saveTags"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:gravity="center"
android:paddingStart="32dp"
android:paddingTop="12dp"
android:paddingEnd="32dp"
android:paddingBottom="12dp"
android:text="@string/save"
app:cornerRadius="25dp"
app:icon="@drawable/ic_save"
app:iconGravity="textStart"
tools:backgroundTint="@color/md_red_400" />
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -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"

View file

@ -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"

View file

@ -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">

View file

@ -54,29 +54,46 @@
app:navigationIcon="@drawable/ic_keyboard_arrow_down_black"
tools:layout_editor_absoluteY="24dp" />
<code.name.monkey.retromusic.views.SeekArc
android:id="@+id/volumeSeekBar"
<FrameLayout
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:padding="24dp"
app:arcColor="?android:attr/colorControlHighlight"
app:arcWidth="4dp"
android:padding="10dp"
app:layout_constraintBottom_toBottomOf="@id/volumeSeekBar"
app:layout_constraintEnd_toEndOf="@id/volumeSeekBar"
app:layout_constraintStart_toStartOf="@id/volumeSeekBar"
app:layout_constraintTop_toTopOf="@id/volumeSeekBar">
<code.name.monkey.retromusic.views.RetroShapeableImageView
android:id="@+id/album_cover"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:srcCompat="@tools:sample/backgrounds/scenic[6]" />
<code.name.monkey.retromusic.views.RetroShapeableImageView
android:id="@+id/album_cover_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0.5"
android:scaleType="centerCrop" />
</FrameLayout>
<me.tankery.lib.circularseekbar.CircularSeekBar
android:id="@+id/volumeSeekBar"
android:layout_width="wrap_content"
android:layout_height="300dp"
app:cs_circle_stroke_width="4dp"
app:cs_end_angle="60"
app:cs_move_outside_circle="true"
app:cs_pointer_color="@color/md_grey_200"
app:cs_pointer_halo_width="0dp"
app:cs_pointer_stroke_width="20dp"
app:cs_start_angle="120"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="@id/guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/playerToolbar"
app:progressWidth="4dp"
app:rotation="180"
app:roundEdges="true"
app:startAngle="30"
app:sweepAngle="300"
app:touchInside="false"
tools:progress="50" />
<com.google.android.material.floatingactionbutton.FloatingActionButton

View file

@ -61,6 +61,7 @@
android:layout_height="match_parent"
android:descendantFocusability="beforeDescendants"
android:focusableInTouchMode="true"
android:overScrollMode="@integer/overScrollMode"
android:transitionGroup="true"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">

View file

@ -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"

View file

@ -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"

View file

@ -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">
<LinearLayout
android:layout_width="match_parent"

View file

@ -24,6 +24,7 @@
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:overScrollMode="@integer/overScrollMode"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<LinearLayout

View file

@ -28,6 +28,7 @@
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:overScrollMode="@integer/overScrollMode"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<LinearLayout

View file

@ -6,23 +6,17 @@
android:layout_height="match_parent"
android:orientation="vertical">
<code.name.monkey.retromusic.views.StatusBarView
android:id="@+id/status_bar"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="?attr/colorSurface"
tools:ignore="UnusedAttribute" />
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface">
android:background="?attr/colorSurface"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorPrimary"
android:fitsSystemWindows="true"
app:liftOnScroll="true">
<com.google.android.material.appbar.MaterialToolbar
@ -37,15 +31,18 @@
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="fill_vertical"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<code.name.monkey.retromusic.views.LollipopFixedWebView
android:id="@+id/license"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="wrap_content"
android:scrollbars="none"
android:fitsSystemWindows="true"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</LinearLayout>

View file

@ -1,8 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.backup.RestoreActivity">
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/backupNameContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="8dp"
android:hint="@string/label_file_name">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/backupName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="false" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textview.MaterialTextView
android:id="@+id/materialTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/choose_restore_title" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/check_settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="true"
android:minHeight="48dp"
android:text="@string/action_settings" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/check_databases"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:text="@string/databases_description" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/check_queue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:text="@string/now_playing_queue" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/check_user_images"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:text="@string/user_images_description" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/check_artist_images"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:text="@string/custom_artist_images" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end">
<com.google.android.material.button.MaterialButton
android:id="@+id/cancel_button"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@string/action_cancel" />
<com.google.android.material.button.MaterialButton
android:id="@+id/restore_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@string/restore" />
</LinearLayout>
</LinearLayout>

View file

@ -1,5 +1,6 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
@ -24,6 +25,7 @@
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="@integer/overScrollMode"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<LinearLayout
@ -32,6 +34,28 @@
android:orientation="vertical"
android:padding="16dp">
<code.name.monkey.retromusic.views.WidthFitSquareCardView
android:id="@+id/imageContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="32dp"
android:transitionName="@string/transition_album_art"
app:cardCornerRadius="@dimen/m3_card_large_radius"
app:cardElevation="8dp"
app:cardUseCompatPadding="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/editorImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:srcCompat="@tools:sample/backgrounds/scenic[5]" />
</code.name.monkey.retromusic.views.WidthFitSquareCardView>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/songTextContainer"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
@ -142,7 +166,6 @@
android:layout_weight="1"
app:hintEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/genreText"
android:layout_width="match_parent"
@ -165,7 +188,6 @@
android:layout_weight="1"
app:hintEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/yearText"
android:layout_width="match_parent"
@ -179,26 +201,56 @@
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/trackNumberContainer"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:hintEnabled="true">
android:baselineAligned="false"
android:orientation="horizontal">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/trackNumberText"
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/trackNumberContainer"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_vertical"
android:hint="@string/track_hint"
android:inputType="text|number"
android:maxLines="1" />
android:layout_marginTop="8dp"
android:layout_weight="1"
app:hintEnabled="true">
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/trackNumberText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_vertical"
android:hint="@string/track_hint"
android:inputType="text|number"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/discNumberContainer"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_weight="1"
app:hintEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/discNumberText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_vertical"
android:hint="@string/disc_hint"
android:inputType="text|number"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/lyricsContainer"

View file

@ -4,6 +4,7 @@
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="@integer/overScrollMode"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<include

View file

@ -59,6 +59,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/songTitle"

View file

@ -33,6 +33,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">
<androidx.constraintlayout.widget.ConstraintLayout

View file

@ -60,6 +60,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false"
android:overScrollMode="@integer/overScrollMode"
app:barrierDirection="bottom"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintStart_toStartOf="parent"

View file

@ -32,6 +32,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">
<androidx.constraintlayout.widget.ConstraintLayout

View file

@ -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">
<code.name.monkey.retromusic.views.insets.InsetsConstraintLayout

View file

@ -17,15 +17,11 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?colorSurface"
android:clickable="true"
android:focusable="true"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface" />
<FrameLayout
android:id="@+id/statusBarContainer"
android:layout_width="match_parent"
@ -85,23 +81,45 @@
app:layout_constraintTop_toBottomOf="@+id/titleContainer"
tools:text="@tools:sample/lorem/random" />
<code.name.monkey.retromusic.views.SeekArc
<FrameLayout
android:layout_width="0dp"
android:layout_height="0dp"
android:padding="10dp"
app:layout_constraintBottom_toBottomOf="@id/volumeSeekBar"
app:layout_constraintEnd_toEndOf="@id/volumeSeekBar"
app:layout_constraintStart_toStartOf="@id/volumeSeekBar"
app:layout_constraintTop_toTopOf="@id/volumeSeekBar">
<code.name.monkey.retromusic.views.RetroShapeableImageView
android:id="@+id/album_cover"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:srcCompat="@tools:sample/backgrounds/scenic[6]" />
<code.name.monkey.retromusic.views.RetroShapeableImageView
android:id="@+id/album_cover_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0.5"
android:scaleType="centerCrop" />
</FrameLayout>
<me.tankery.lib.circularseekbar.CircularSeekBar
android:id="@+id/volumeSeekBar"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="300dp"
android:padding="28dp"
app:arcColor="?android:attr/colorControlHighlight"
app:arcWidth="4dp"
app:cs_circle_stroke_width="4dp"
app:cs_end_angle="60"
app:cs_move_outside_circle="true"
app:cs_pointer_color="@color/md_grey_200"
app:cs_pointer_halo_width="0dp"
app:cs_pointer_stroke_width="20dp"
app:cs_start_angle="120"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:progressWidth="4dp"
app:rotation="180"
app:roundEdges="true"
app:startAngle="30"
app:sweepAngle="300"
app:touchInside="false"
tools:progress="50" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
@ -139,14 +157,15 @@
app:tint="@color/md_green_500" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/materialTextView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/volume"
android:textAppearance="@style/TextViewSubtitle2"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintBottom_toBottomOf="@+id/volumeSeekBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/volumeSeekBar" />
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/volumeSeekBar" />
<androidx.appcompat.widget.AppCompatSeekBar

View file

@ -140,7 +140,8 @@
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/item_list" />
android:overScrollMode="@integer/overScrollMode"
tools:listitem="@layout/item_list" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View file

@ -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"

View file

@ -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" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -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"

View file

@ -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">
<code.name.monkey.retromusic.views.insets.InsetsConstraintLayout

View file

@ -10,7 +10,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/songCurrentProgress"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:gravity="center"
@ -41,7 +41,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/songTotalTime"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:gravity="center"

View file

@ -11,6 +11,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:overScrollMode="@integer/overScrollMode"
android:scrollbars="none"
android:transitionGroup="true"
app:layout_dodgeInsetEdges="bottom"

View file

@ -46,7 +46,8 @@
android:scrollbars="none"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
tools:listitem="@layout/item_list"
android:transitionGroup="true"/>
android:overScrollMode="@integer/overScrollMode"
android:transitionGroup="true" />
<LinearLayout
android:id="@android:id/empty"

View file

@ -4,7 +4,8 @@
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:overScrollMode="@integer/overScrollMode">
<LinearLayout
android:id="@+id/container"

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -7,49 +8,24 @@
<androidx.viewpager.widget.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
android:overScrollMode="@integer/overScrollMode">
<FrameLayout
android:id="@+id/playerLyrics"
</androidx.viewpager.widget.ViewPager>
<code.name.monkey.retromusic.lyrics.CoverLrcView
android:id="@+id/lyricsView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:alpha="0"
android:clipToPadding="false"
android:elevation="20dp"
android:layout_height="match_parent"
android:layout_margin="16dp"
android:visibility="gone"
tools:visibility="visible">
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" />
<View
android:id="@+id/mask_lyrics"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/lyrics_mask" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/player_lyrics_line1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_margin="16dp"
android:gravity="center"
android:shadowColor="@color/md_black_1000"
android:shadowRadius="4"
android:textAppearance="@style/TextViewHeadline5"
android:textColor="@color/md_white_1000"
android:visibility="gone" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/player_lyrics_line2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_margin="16dp"
android:gravity="center"
android:shadowColor="@color/md_black_1000"
android:shadowRadius="4"
android:textAppearance="@style/TextViewHeadline5"
android:textColor="@color/md_white_1000" />
</FrameLayout>
</FrameLayout>

View file

@ -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"

View file

@ -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" />
<LinearLayout

View file

@ -73,7 +73,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
android:scrollbarSize="0dp">
android:scrollbars="none"
android:overScrollMode="@integer/overScrollMode">
<com.google.android.material.chip.ChipGroup
android:id="@+id/searchFilterGroup"
@ -126,6 +127,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:overScrollMode="@integer/overScrollMode"
android:scrollbarStyle="outsideOverlay"
android:scrollbars="vertical"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />

View file

@ -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" />

View file

@ -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" />

View file

@ -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"

View file

@ -29,7 +29,6 @@
<item name="android:windowLightNavigationBar">false</item>
<item name="colorSurface">@color/darkColorSurface</item>
<item name="materialCardViewStyle">@style/Widget.MaterialComponents.CardView</item>
<item name="bottomSheetTint">@color/bottomSheetColor</item>
<item name="elevationOverlayColor">@color/elevationOverlayDark</item>
<item name="floatingActionButtonStyle">
@style/Widget.MaterialComponents.FloatingActionButton

View file

@ -27,7 +27,6 @@
<item name="bottomNavigationStyle">@style/Widget.Material3.BottomNavigationView</item>
<item name="materialButtonStyle">@style/MaterialButtonTheme</item>
<item name="materialCardViewStyle">@style/Widget.Material3.CardView.Elevated</item>
<item name="bottomSheetTint">@color/bottomSheetColor</item>
<item name="elevationOverlayColor">@color/elevationOverlay</item>
<item name="floatingActionButtonStyle">
@style/Widget.MaterialComponents.FloatingActionButton
@ -54,7 +53,6 @@
<item name="bottomNavigationStyle">@style/Widget.Material3.BottomNavigationView</item>
<item name="android:windowBackground">@color/window_color_dark</item>
<item name="materialCardViewStyle">@style/Widget.Material3.CardView.Elevated</item>
<item name="bottomSheetTint">@color/bottomSheetColor</item>
<item name="elevationOverlayColor">@color/elevationOverlay</item>
<item name="floatingActionButtonStyle">
@style/Widget.MaterialComponents.FloatingActionButton
@ -78,7 +76,6 @@
<item name="materialButtonStyle">@style/MaterialButtonTheme</item>
<item name="bottomNavigationStyle">@style/Widget.Material3.BottomNavigationView</item>
<item name="materialCardViewStyle">@style/Widget.Material3.CardView.Elevated</item>
<item name="bottomSheetTint">@color/bottomSheetColorLight</item>
<item name="elevationOverlayColor">@color/elevationOverlayLight</item>
<item name="floatingActionButtonStyle">
@style/Widget.MaterialComponents.FloatingActionButton

View file

@ -4,4 +4,5 @@
<bool name="md3_enabled">true</bool>
<bool name="colored_notification_available">false</bool>
<integer name="overScrollMode">0</integer>
</resources>

View file

@ -6,7 +6,6 @@
<attr name="defaultFooterColor" format="color" />
<attr name="toolbarPopupTheme" format="reference" />
<attr name="lineHeightHint" format="dimension" />
<attr name="bottomSheetTint" format="reference" />
<declare-styleable name="NetworkImageView">
<attr name="url_link" format="string" />

View file

@ -28,4 +28,6 @@
<bool name="md3_enabled">false</bool>
<bool name="colored_notification_available">true</bool>
<integer name="overScrollMode">2</integer>
<bool name="allowBackup">true</bool>
</resources>

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="lrc_animation_duration">1000</integer>
<dimen name="lrc_text_size">16sp</dimen>
<dimen name="lrc_time_text_size">12sp</dimen>
<dimen name="lrc_text_size">20sp</dimen>
<dimen name="lrc_time_text_size">16sp</dimen>
<dimen name="lrc_divider_height">16dp</dimen>
<dimen name="lrc_timeline_height">1dp</dimen>
<dimen name="lrc_drawable_width">30dp</dimen>

View file

@ -73,6 +73,7 @@
<string name="app_widget_big_name">Full Image</string>
<string name="app_widget_card_name">Card</string>
<string name="app_widget_classic_name">Classic</string>
<string name="app_widget_md3_name">MD3</string>
<string name="app_widget_small_name">Small</string>
<string name="app_widget_text_name">Minimal Text</string>
<string name="artist">Artist</string>
@ -81,6 +82,7 @@
<string name="audio_focus_denied">Audio focus denied.</string>
<string name="audio_settings_summary">Change the sound settings and adjust the equalizer controls</string>
<string name="auto">Auto</string>
<string name="backup_restore_settings_summary">Backup and restore your settings, playlists</string>
<string name="backup_restore_title">
<![CDATA[Backup & Restore]]>
</string>
@ -113,6 +115,7 @@
<string name="cascading">Cascading</string>
<string name="changelog">Changelog</string>
<string name="changelog_summary">Check out What\'s New</string>
<string name="choose_restore_title">Choose what to restore</string>
<string name="circle">Circle</string>
<string name="circular">Circular</string>
<string name="classic">Classic</string>
@ -132,7 +135,9 @@
<string name="created_playlist_x">Created playlist %1$s.</string>
<string name="credit_title">Members and contributors </string>
<string name="currently_listening_to_x_by_x">Currently listening to %1$s by %2$s.</string>
<string name="custom_artist_images">Custom Artist Images</string>
<string name="dark_theme_name">Kinda Dark</string>
<string name="databases_description">Databases (Playlists, History, Most Played, etc.)</string>
<string name="delete_playlist_title">Delete playlist</string>
<string name="delete_playlist_x">
<![CDATA[Delete the playlist <b>%1$s</b>?]]>
@ -156,6 +161,7 @@
<string name="device_info">Device info</string>
<string name="dialog_message_set_ringtone">Allow Retro Music to modify audio settings</string>
<string name="dialog_title_set_ringtone">Set ringtone</string>
<string name="disc_hint">Disc Number</string>
<string name="do_you_want_to_clear_the_blacklist">Do you want to clear the blacklist?</string>
<string name="do_you_want_to_remove_from_the_blacklist">
<![CDATA[Do you want to remove <b>%1$s</b> from the blacklist?]]>
@ -404,6 +410,7 @@
<string name="reset_action">Reset</string>
<string name="reset_artist_image">Reset artist image</string>
<string name="restore">Restore</string>
<string name="restore_message">Do you want to restore backup?</string>
<string name="restored_previous_purchase_please_restart">Restored previous purchase. Please restart the app to make use of all features.</string>
<string name="restored_previous_purchases">Restored previous purchases.</string>
<string name="restoring_purchase">Restoring purchase…</string>
@ -453,8 +460,8 @@
<string name="sort_order">Sort order</string>
<string name="sort_order_a_z">Ascending</string>
<string name="sort_order_album">Album</string>
<string name="sort_order_artist">Artist</string>
<string name="sort_order_album_artist">@string/album_artist</string>
<string name="sort_order_artist">Artist</string>
<string name="sort_order_composer">Composer</string>
<string name="sort_order_date">Date added</string>
<string name="sort_order_date_modified">Date modified</string>
@ -480,6 +487,7 @@
<string name="tiny">Tiny</string>
<string name="tiny_card_style">Tiny card</string>
<string name="title">Title</string>
<string name="title_new_backup">New Backup</string>
<string name="today">Today</string>
<string name="top_albums">Top albums</string>
<string name="top_artists">Top artists</string>
@ -495,6 +503,7 @@
<string name="up_next">Up next</string>
<string name="update_image">Update image</string>
<string name="updating">Updating…</string>
<string name="user_images_description">User Images</string>
<string name="user_name">User Name</string>
<string name="username">Username</string>
<string name="version">Version</string>
@ -515,8 +524,4 @@
<string name="you_have_to_select_at_least_one_category">You have to select at least one category.</string>
<string name="you_will_be_forwarded_to_the_issue_tracker_website">You will be forwarded to the issue tracker website.</string>
<string name="your_account_data_is_only_used_for_authentication">Your account data is only used for authentication.</string>
<string name="restore_message">Do you want to restore backup?</string>
<string name="title_new_backup">New Backup</string>
<string name="backup_restore_settings_summary">Backup and restore your settings, playlists</string>
<string name="app_widget_md3_name">MD3</string>
</resources>

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