Merge branch 'dev' into l10n_dev

This commit is contained in:
Prathamesh More 2022-06-21 23:26:47 +05:30 committed by GitHub
commit 26d7012e46
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
111 changed files with 2010 additions and 2159 deletions

View file

@ -38,12 +38,12 @@ ___
## 📱 Screenshots ## 📱 Screenshots
### App Themes ### App Themes
| <img src="screenshots/home_light.jpg" width="200"/> | <img src="screenshots/home_dark.jpg" width="200"/> | <img src="screenshots/home_black.jpg" width="200"/> | | <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg" width="200"/> | <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg" width="200"/> | <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/4.jpg" width="200"/> |
|:---:|:---:|:---:| |:---:|:---:|:---:|
|Clearly white| Kinda dark | Just black| |Clearly white| Kinda dark | Just black|
### Player screen ### Player screen
| <img src="screenshots/home_light.jpg" width="200"/>| <img src="screenshots/songs.jpg" width="200"/>| <img src="screenshots/albums.jpg" width="200"/>| <img src="screenshots/artists.jpg" width="200"/>| <img src="screenshots/settings.jpg" width="200"/>| | <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg" width="200"/>| <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/5.jpg" width="200"/>| <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/6.jpg" width="200"/>| <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/7.jpg" width="200"/>| <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/8.jpg" width="200"/>|
|:---:|:---:|:---:|:---:|:---:| |:---:|:---:|:---:|:---:|:---:|
| Home | Songs | Albums | Artists | Settings | | Home | Songs | Albums | Artists | Settings |
@ -58,7 +58,7 @@ ___
| Synced Replace Cover light | Synced Replace Cover dark | Synced Replace Cover black | | Synced Replace Cover light | Synced Replace Cover dark | Synced Replace Cover black |
### 10+ Now playing themes ### 10+ Now playing themes
| <img src="screenshots/normal.jpg" width="200"/> |<img src="screenshots/fit.jpg" width="200"/>| <img src="screenshots/flat.jpg" width="200"/> | <img src="screenshots/color.jpg" width="200"/> | <img src="screenshots/material.jpg" width="200"/> | | <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg" width="200"/> |<img src="screenshots/fit.jpg" width="200"/>| <img src="screenshots/flat.jpg" width="200"/> | <img src="screenshots/color.jpg" width="200"/> | <img src="screenshots/material.jpg" width="200"/> |
|:-----: |:-----: |:-----: |:-----: |:-----: | |:-----: |:-----: |:-----: |:-----: |:-----: |
| Normal | Fit | Flat | Color | Material | | Normal | Fit | Flat | Color | Material |

View file

@ -14,8 +14,8 @@ android {
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
applicationId "code.name.monkey.retromusic" applicationId "code.name.monkey.retromusic"
versionCode 10590 versionCode 10596
versionName '6.0.0-beta' versionName '6.0.2-beta'
buildConfigField("String", "GOOGLE_PLAY_LICENSING_KEY", "\"${getProperty(getProperties('../public.properties'), 'GOOGLE_PLAY_LICENSE_KEY')}\"") buildConfigField("String", "GOOGLE_PLAY_LICENSING_KEY", "\"${getProperty(getProperties('../public.properties'), 'GOOGLE_PLAY_LICENSE_KEY')}\"")
} }
@ -41,6 +41,15 @@ android {
versionNameSuffix ' DEBUG' versionNameSuffix ' DEBUG'
} }
} }
flavorDimensions "version"
productFlavors {
normal {
dimension "version"
}
fdroid {
dimension "version"
}
}
buildFeatures{ buildFeatures{
viewBinding true viewBinding true
@ -88,44 +97,43 @@ dependencies {
implementation "androidx.gridlayout:gridlayout:1.0.0" implementation "androidx.gridlayout:gridlayout:1.0.0"
implementation "androidx.appcompat:appcompat:$appcompat_version" implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation 'androidx.annotation:annotation:1.3.0' implementation 'androidx.annotation:annotation:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation "androidx.preference:preference-ktx:$preference_version" implementation "androidx.preference:preference-ktx:$preference_version"
implementation 'androidx.core:core-ktx:1.8.0' implementation "androidx.core:core-ktx:$core_version"
implementation 'androidx.palette:palette-ktx:1.0.0' implementation 'androidx.palette:palette-ktx:1.0.0'
//Cast Dependencies
implementation 'androidx.mediarouter:mediarouter:1.3.0' implementation 'androidx.mediarouter:mediarouter:1.3.0'
implementation 'com.google.android.gms:play-services-cast-framework:21.0.1' //Cast Dependencies
normalImplementation 'com.google.android.gms:play-services-cast-framework:21.0.1'
//WebServer by NanoHttpd //WebServer by NanoHttpd
implementation "org.nanohttpd:nanohttpd:2.3.1" normalImplementation "org.nanohttpd:nanohttpd:2.3.1"
implementation "androidx.navigation:navigation-runtime-ktx:$navigation_version" implementation "androidx.navigation:navigation-runtime-ktx:$navigation_version"
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version" implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
implementation "androidx.navigation:navigation-ui-ktx:$navigation_version" implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
def room_version = '2.4.2' def room_version = '2.5.0-alpha02'
implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version" implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version" kapt "androidx.room:room-compiler:$room_version"
def lifecycle_version = "2.5.0-rc01"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
implementation "androidx.core:core-splashscreen:1.0.0-rc01" implementation "androidx.core:core-splashscreen:1.0.0-rc01"
implementation 'com.google.android.play:feature-delivery:2.0.0' normalImplementation 'com.google.android.play:feature-delivery:2.0.0'
implementation 'com.google.android.play:review:2.0.0' normalImplementation 'com.google.android.play:review:2.0.0'
implementation "com.google.android.material:material:$mdc_version" implementation "com.google.android.material:material:$mdc_version"
def retrofit_version = '2.9.0' def retrofit_version = '2.9.0'
implementation "com.squareup.retrofit2:retrofit:$retrofit_version" implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version" implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.7' implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.9'
def material_dialog_version = "3.3.0" def material_dialog_version = "3.3.0"
implementation "com.afollestad.material-dialogs:core:$material_dialog_version" implementation "com.afollestad.material-dialogs:core:$material_dialog_version"
@ -160,12 +168,11 @@ dependencies {
implementation 'org.eclipse.mylyn.github:org.eclipse.egit.github.core:2.1.5' implementation 'org.eclipse.mylyn.github:org.eclipse.egit.github.core:2.1.5'
implementation 'com.github.Adonai:jaudiotagger:2.3.15' implementation 'com.github.Adonai:jaudiotagger:2.3.15'
implementation 'com.anjlab.android.iab.v3:library:2.0.3' normalImplementation 'com.anjlab.android.iab.v3:library:2.0.3'
implementation 'com.r0adkll:slidableactivity:2.1.0' implementation 'com.r0adkll:slidableactivity:2.1.0'
implementation 'com.heinrichreimersoftware:material-intro:2.0.0' implementation 'com.heinrichreimersoftware:material-intro:2.0.0'
implementation 'com.github.dhaval2404:imagepicker:2.1' implementation 'com.github.dhaval2404:imagepicker:2.1'
implementation 'me.zhanghai.android.fastscroll:library:1.1.8' implementation 'me.zhanghai.android.fastscroll:library:1.1.8'
implementation 'cat.ereza:customactivityoncrash:2.4.0' implementation 'cat.ereza:customactivityoncrash:2.4.0'
implementation 'me.tankery.lib:circularSeekBar:1.4.0' implementation 'me.tankery.lib:circularSeekBar:1.4.0'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
} }

View file

@ -0,0 +1,5 @@
package code.name.monkey.retromusic.activities.base
abstract class AbsCastActivity : AbsSlidingMusicPanelActivity() {
}

View file

@ -0,0 +1,12 @@
package code.name.monkey.retromusic.billing
import android.content.Context
@Suppress("UNUSED_PARAMETER")
class BillingManager(context: Context) {
fun release() {}
val isProVersion: Boolean
get() = true
}

View file

@ -0,0 +1,6 @@
package code.name.monkey.retromusic.cast
import android.content.Context
@Suppress("UNUSED_PARAMETER")
class RetroWebServer(context: Context)

View file

@ -0,0 +1,15 @@
@file:Suppress("UNUSED_PARAMETER", "unused")
package code.name.monkey.retromusic.extensions
import android.content.Context
import android.view.Menu
import androidx.fragment.app.FragmentActivity
fun Context.setUpMediaRouteButton(menu: Menu) {}
fun FragmentActivity.installLanguageAndRecreate(code: String) {}
fun Context.goToProVersion() {}
fun Context.installSplitCompat() {}

View file

@ -0,0 +1,47 @@
package code.name.monkey.retromusic.service
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.service.playback.Playback
// Empty CastPlayer implementation
class CastPlayer : Playback {
override val isInitialized: Boolean
get() = true
override val isPlaying: Boolean
get() = true
override val audioSessionId: Int
get() = 0
override fun setDataSource(
song: Song,
force: Boolean,
completion: (success: Boolean) -> Unit,
) {
}
override fun setNextDataSource(path: String?) {}
override var callbacks: Playback.PlaybackCallbacks? = null
override fun start() = true
override fun stop() {}
override fun release() {}
override fun pause(): Boolean = true
override fun duration() = 0
override fun position() = 0
override fun seek(whereto: Int) = whereto
override fun setVolume(vol: Float) = true
override fun setAudioSessionId(sessionId: Int) = true
override fun setCrossFadeDuration(duration: Int) {}
override fun setPlaybackSpeedPitch(speed: Float, pitch: Float) {}
}

View file

@ -0,0 +1,8 @@
package code.name.monkey.retromusic.util
import android.content.Context
@Suppress("UNUSED_PARAMETER")
object AppRater {
fun appLaunched(context: Context) {}
}

View file

@ -13,7 +13,7 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission <uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" /> android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" /> <uses-permission android:name="android.permission.BROADCAST_STICKY" />
@ -22,7 +22,6 @@
android:name="android.permission.WRITE_SETTINGS" android:name="android.permission.WRITE_SETTINGS"
tools:ignore="ProtectedPermissions" /> tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="com.android.vending.BILLING" />
<uses-permission <uses-permission
android:name="android.permission.BLUETOOTH_CONNECT" android:name="android.permission.BLUETOOTH_CONNECT"
android:usesPermissionFlags="neverForLocation" android:usesPermissionFlags="neverForLocation"
@ -34,12 +33,12 @@
android:configChanges="locale|layoutDirection" android:configChanges="locale|layoutDirection"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:restoreAnyVersion="true" android:restoreAnyVersion="true"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.RetroMusic.FollowSystem" android:theme="@style/Theme.RetroMusic.FollowSystem"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
android:requestLegacyExternalStorage="true"
tools:targetApi="m"> tools:targetApi="m">
<activity <activity
android:name=".activities.MainActivity" android:name=".activities.MainActivity"
@ -120,7 +119,6 @@
<activity android:name=".activities.tageditor.SongTagEditorActivity" /> <activity android:name=".activities.tageditor.SongTagEditorActivity" />
<activity android:name=".activities.SupportDevelopmentActivity" /> <activity android:name=".activities.SupportDevelopmentActivity" />
<activity android:name=".activities.LicenseActivity" /> <activity android:name=".activities.LicenseActivity" />
<activity android:name=".activities.PurchaseActivity" />
<activity android:name=".activities.bugreport.BugReportActivity" /> <activity android:name=".activities.bugreport.BugReportActivity" />
<activity android:name=".activities.ShareInstagramStory" /> <activity android:name=".activities.ShareInstagramStory" />
<activity android:name=".activities.DriveModeActivity" /> <activity android:name=".activities.DriveModeActivity" />
@ -194,7 +192,7 @@
</provider> </provider>
<receiver <receiver
android:name="androidx.media.session.MediaButtonReceiver" android:name=".service.MediaButtonIntentReceiver"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" /> <action android:name="android.intent.action.MEDIA_BUTTON" />
@ -305,9 +303,6 @@
<intent-filter> <intent-filter>
<action android:name="android.media.browse.MediaBrowserService" /> <action android:name="android.media.browse.MediaBrowserService" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</service> </service>
<meta-data <meta-data
@ -318,10 +313,6 @@
android:name="com.lge.support.SPLIT_WINDOW" android:name="com.lge.support.SPLIT_WINDOW"
android:value="true" /> android:value="true" />
<meta-data
android:name="com.android.vending.splits.required"
android:value="true" />
<!-- Android Auto --> <!-- Android Auto -->
<meta-data <meta-data
android:name="com.google.android.gms.car.application" android:name="com.google.android.gms.car.application"
@ -332,11 +323,6 @@
<meta-data <meta-data
android:name="com.google.android.gms.car.notification.SmallIcon" android:name="com.google.android.gms.car.notification.SmallIcon"
android:resource="@drawable/ic_notification" /> android:resource="@drawable/ic_notification" />
<!-- ChromeCast -->
<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="code.name.monkey.retromusic.cast.CastOptionsProvider" />
</application> </application>
<!-- <!--

View file

@ -62,6 +62,25 @@
</head> </head>
<body> <body>
<div>
<h5>June 21, 2022</h5>
<h2>v6.0.2<span class="tag"><i>Beta</i></span></h2>
<h3>Fixed</h3>
<ul>
<li>Minor bug fixes and improvements</li>
</ul>
</div>
<div>
<h5>June 13, 2022</h5>
<h2>v6.0.1<span class="tag"><i>Beta</i></span></h2>
<h3>Fixed</h3>
<ul>
<li>Fixed ChromeCast crash</li>
<li>Fixed Slider crashes</li>
<li>Fixed storage related crashes on Android 10</li>
<li>Fixed CrossFade not working Fade Audio is not working</li>
</ul>
</div>
<div> <div>
<h5>June 7, 2022</h5> <h5>June 7, 2022</h5>
<h2>v6.0.0<span class="tag"><i>Beta</i></span></h2> <h2>v6.0.0<span class="tag"><i>Beta</i></span></h2>

View file

@ -19,20 +19,17 @@ import androidx.preference.PreferenceManager
import cat.ereza.customactivityoncrash.config.CaocConfig import cat.ereza.customactivityoncrash.config.CaocConfig
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.VersionUtils import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.Constants.PRO_VERSION_PRODUCT_ID
import code.name.monkey.retromusic.activities.ErrorActivity import code.name.monkey.retromusic.activities.ErrorActivity
import code.name.monkey.retromusic.activities.MainActivity import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.appshortcuts.DynamicShortcutManager import code.name.monkey.retromusic.appshortcuts.DynamicShortcutManager
import code.name.monkey.retromusic.extensions.showToast import code.name.monkey.retromusic.billing.BillingManager
import code.name.monkey.retromusic.helper.WallpaperAccentManager import code.name.monkey.retromusic.helper.WallpaperAccentManager
import com.anjlab.android.iab.v3.BillingProcessor
import com.anjlab.android.iab.v3.PurchaseInfo
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin import org.koin.core.context.startKoin
class App : Application() { class App : Application() {
lateinit var billingProcessor: BillingProcessor lateinit var billingManager: BillingManager
private val wallpaperAccentManager = WallpaperAccentManager(this) private val wallpaperAccentManager = WallpaperAccentManager(this)
override fun onCreate() { override fun onCreate() {
@ -55,33 +52,20 @@ class App : Application() {
if (VersionUtils.hasNougatMR()) if (VersionUtils.hasNougatMR())
DynamicShortcutManager(this).initDynamicShortcuts() DynamicShortcutManager(this).initDynamicShortcuts()
// automatically restores purchases billingManager = BillingManager(this)
billingProcessor = BillingProcessor(
this, BuildConfig.GOOGLE_PLAY_LICENSING_KEY,
object : BillingProcessor.IBillingHandler {
override fun onProductPurchased(productId: String, details: PurchaseInfo?) {}
override fun onPurchaseHistoryRestored() {
showToast(R.string.restored_previous_purchase_please_restart)
}
override fun onBillingError(errorCode: Int, error: Throwable?) {}
override fun onBillingInitialized() {}
})
// setting Error activity // setting Error activity
CaocConfig.Builder.create().errorActivity(ErrorActivity::class.java) CaocConfig.Builder.create().errorActivity(ErrorActivity::class.java)
.restartActivity(MainActivity::class.java).apply() .restartActivity(MainActivity::class.java).apply()
// Set Default values for now playing preferences // Set Default values for now playing preferences
// This will reduce start time for now playing settings fragment as Preference listener of AbsSlidingMusicPanelActivity won't be called // This will reduce startup time for now playing settings fragment as Preference listener of AbsSlidingMusicPanelActivity won't be called
PreferenceManager.setDefaultValues(this, R.xml.pref_now_playing_screen, false) PreferenceManager.setDefaultValues(this, R.xml.pref_now_playing_screen, false)
} }
override fun onTerminate() { override fun onTerminate() {
super.onTerminate() super.onTerminate()
billingProcessor.release() billingManager.release()
wallpaperAccentManager.release() wallpaperAccentManager.release()
} }
@ -93,9 +77,7 @@ class App : Application() {
} }
fun isProVersion(): Boolean { fun isProVersion(): Boolean {
return BuildConfig.DEBUG || instance?.billingProcessor!!.isPurchased( return BuildConfig.DEBUG || instance?.billingManager!!.isProVersion
PRO_VERSION_PRODUCT_ID
)
} }
} }
} }

View file

@ -136,7 +136,6 @@ const val ARTIST_DETAIL_SONG_SORT_ORDER = "artist_detail_song_sort_order"
const val LYRICS_OPTIONS = "lyrics_tab_position" const val LYRICS_OPTIONS = "lyrics_tab_position"
const val CHOOSE_EQUALIZER = "choose_equalizer" const val CHOOSE_EQUALIZER = "choose_equalizer"
const val EQUALIZER = "equalizer" const val EQUALIZER = "equalizer"
const val TOGGLE_SHUFFLE = "toggle_shuffle"
const val SONG_GRID_STYLE = "song_grid_style" const val SONG_GRID_STYLE = "song_grid_style"
const val PAUSE_ON_ZERO_VOLUME = "pause_on_zero_volume" const val PAUSE_ON_ZERO_VOLUME = "pause_on_zero_volume"
const val FILTER_SONG = "filter_song" const val FILTER_SONG = "filter_song"

View file

@ -14,12 +14,10 @@
*/ */
package code.name.monkey.retromusic.activities package code.name.monkey.retromusic.activities
import android.animation.ObjectAnimator
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.os.Bundle import android.os.Bundle
import android.view.animation.LinearInterpolator
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
@ -27,7 +25,6 @@ import code.name.monkey.retromusic.databinding.ActivityDriveModeBinding
import code.name.monkey.retromusic.db.toSongEntity import code.name.monkey.retromusic.db.toSongEntity
import code.name.monkey.retromusic.extensions.accentColor import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.drawAboveSystemBars import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.glide.BlurTransformation import code.name.monkey.retromusic.glide.BlurTransformation
import code.name.monkey.retromusic.glide.GlideApp import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.RetroGlideExtension
@ -246,12 +243,10 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
} }
override fun onUpdateProgressViews(progress: Int, total: Int) { override fun onUpdateProgressViews(progress: Int, total: Int) {
binding.progressSlider.valueTo = total.toFloat() binding.progressSlider.run {
valueTo = total.toFloat()
val animator = ObjectAnimator.ofFloat(binding.progressSlider, "value", progress.toFloat()) value = progress.toFloat().coerceIn(valueFrom, valueTo)
animator.duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME }
animator.interpolator = LinearInterpolator()
animator.start()
binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())

View file

@ -14,41 +14,23 @@
*/ */
package code.name.monkey.retromusic.activities package code.name.monkey.retromusic.activities
import android.graphics.Paint
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.BuildConfig
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsThemeActivity import code.name.monkey.retromusic.activities.base.AbsThemeActivity
import code.name.monkey.retromusic.databinding.ActivityDonationBinding import code.name.monkey.retromusic.databinding.ActivityDonationBinding
import code.name.monkey.retromusic.databinding.ItemDonationOptionBinding import code.name.monkey.retromusic.extensions.setStatusBarColorAuto
import code.name.monkey.retromusic.extensions.* import code.name.monkey.retromusic.extensions.setTaskDescriptionColorAuto
import com.anjlab.android.iab.v3.BillingProcessor import code.name.monkey.retromusic.extensions.surfaceColor
import com.anjlab.android.iab.v3.PurchaseInfo
import com.anjlab.android.iab.v3.SkuDetails
class SupportDevelopmentActivity : AbsThemeActivity(), BillingProcessor.IBillingHandler { class SupportDevelopmentActivity : AbsThemeActivity() {
lateinit var binding: ActivityDonationBinding lateinit var binding: ActivityDonationBinding
companion object { companion object {
val TAG: String = SupportDevelopmentActivity::class.java.simpleName val TAG: String = SupportDevelopmentActivity::class.java.simpleName
const val DONATION_PRODUCT_IDS = R.array.donation_ids
} }
var billingProcessor: BillingProcessor? = null
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) { if (item.itemId == android.R.id.home) {
onBackPressed() onBackPressed()
@ -57,11 +39,6 @@ class SupportDevelopmentActivity : AbsThemeActivity(), BillingProcessor.IBilling
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
fun donate(i: Int) {
val ids = resources.getStringArray(DONATION_PRODUCT_IDS)
billingProcessor?.purchase(this, ids[i])
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityDonationBinding.inflate(layoutInflater) binding = ActivityDonationBinding.inflate(layoutInflater)
@ -72,148 +49,11 @@ class SupportDevelopmentActivity : AbsThemeActivity(), BillingProcessor.IBilling
setupToolbar() setupToolbar()
billingProcessor = BillingProcessor(this, BuildConfig.GOOGLE_PLAY_LICENSING_KEY, this)
TintHelper.setTint(binding.progress, accentColor())
binding.donation.setTextColor(accentColor())
} }
private fun setupToolbar() { private fun setupToolbar() {
val toolbarColor = surfaceColor() binding.toolbar.setBackgroundColor(surfaceColor())
binding.toolbar.setBackgroundColor(toolbarColor)
ToolbarContentTintHelper.colorBackButton(binding.toolbar) ToolbarContentTintHelper.colorBackButton(binding.toolbar)
setSupportActionBar(binding.toolbar) setSupportActionBar(binding.toolbar)
} }
override fun onBillingInitialized() {
loadSkuDetails()
}
private fun loadSkuDetails() {
binding.progressContainer.isVisible = true
binding.recyclerView.isVisible = false
val ids = resources.getStringArray(DONATION_PRODUCT_IDS)
billingProcessor!!.getPurchaseListingDetailsAsync(
ArrayList(listOf(*ids)),
object : BillingProcessor.ISkuDetailsResponseListener {
override fun onSkuDetailsResponse(skuDetails: MutableList<SkuDetails>?) {
if (skuDetails == null || skuDetails.isEmpty()) {
binding.progressContainer.isVisible = false
return
}
binding.progressContainer.isVisible = false
binding.recyclerView.apply {
itemAnimator = DefaultItemAnimator()
layoutManager = GridLayoutManager(this@SupportDevelopmentActivity, 2)
adapter = SkuDetailsAdapter(this@SupportDevelopmentActivity, skuDetails)
isVisible = true
}
}
override fun onSkuDetailsError(error: String?) {
Log.e(TAG, error.toString())
}
})
}
override fun onProductPurchased(productId: String, details: PurchaseInfo?) {
// loadSkuDetails();
showToast(R.string.thank_you)
}
override fun onBillingError(errorCode: Int, error: Throwable?) {
Log.e(TAG, "Billing error: code = $errorCode", error)
}
override fun onPurchaseHistoryRestored() {
// loadSkuDetails();
showToast(R.string.restored_previous_purchases)
}
override fun onDestroy() {
billingProcessor?.release()
super.onDestroy()
}
}
class SkuDetailsAdapter(
private var donationsDialog: SupportDevelopmentActivity,
objects: List<SkuDetails>,
) : RecyclerView.Adapter<SkuDetailsAdapter.ViewHolder>() {
private var skuDetailsList: List<SkuDetails> = ArrayList()
init {
skuDetailsList = objects
}
private fun getIcon(position: Int): Int {
return when (position) {
0 -> R.drawable.ic_cookie
1 -> R.drawable.ic_take_away
2 -> R.drawable.ic_take_away_coffe
3 -> R.drawable.ic_beer
4 -> R.drawable.ic_fast_food_meal
5 -> R.drawable.ic_popcorn
6 -> R.drawable.ic_card_giftcard
7 -> R.drawable.ic_food_croissant
else -> R.drawable.ic_card_giftcard
}
}
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): ViewHolder {
return ViewHolder(
ItemDonationOptionBinding.inflate(
LayoutInflater.from(donationsDialog),
viewGroup,
false
)
)
}
override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
val skuDetails = skuDetailsList[i]
with(viewHolder.binding) {
itemTitle.text = skuDetails.title.replace("(Retro Music Player MP3 Player)", "")
.trim { it <= ' ' }
itemText.text = skuDetails.description
itemText.isVisible = false
itemPrice.text = skuDetails.priceText
itemImage.setImageResource(getIcon(i))
}
val purchased = donationsDialog.billingProcessor!!.isPurchased(skuDetails.productId)
val titleTextColor = if (purchased) ATHUtil.resolveColor(
donationsDialog,
android.R.attr.textColorHint
) else donationsDialog.textColorPrimary()
val contentTextColor =
if (purchased) titleTextColor else donationsDialog.textColorSecondary()
with(viewHolder.binding) {
itemTitle.setTextColor(titleTextColor)
itemText.setTextColor(contentTextColor)
itemPrice.setTextColor(titleTextColor)
strikeThrough(itemTitle, purchased)
strikeThrough(itemText, purchased)
strikeThrough(itemPrice, purchased)
}
viewHolder.itemView.isEnabled = !purchased
viewHolder.itemView.setOnClickListener { donationsDialog.donate(i) }
}
override fun getItemCount(): Int {
return skuDetailsList.size
}
class ViewHolder(val binding: ItemDonationOptionBinding) : RecyclerView.ViewHolder(binding.root)
companion object {
private fun strikeThrough(textView: TextView, strikeThrough: Boolean) {
textView.paintFlags =
if (strikeThrough) textView.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
else textView.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
}
}
} }

View file

@ -191,7 +191,7 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventLi
override fun getPermissionsToRequest(): Array<String> { override fun getPermissionsToRequest(): Array<String> {
return mutableListOf(Manifest.permission.READ_EXTERNAL_STORAGE).apply { return mutableListOf(Manifest.permission.READ_EXTERNAL_STORAGE).apply {
if (!VersionUtils.hasQ()) { if (!VersionUtils.hasR()) {
add(Manifest.permission.WRITE_EXTERNAL_STORAGE) add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
} }
}.toTypedArray() }.toTypedArray()

View file

@ -234,8 +234,7 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity(),
navigationView.labelVisibilityMode = PreferenceUtil.tabTitleMode navigationView.labelVisibilityMode = PreferenceUtil.tabTitleMode
} }
TOGGLE_FULL_SCREEN -> { TOGGLE_FULL_SCREEN -> {
if (!PreferenceUtil.isFullScreenMode) exitFullscreen() recreate()
setEdgeToEdgeOrImmersive()
} }
SCREEN_ON_LYRICS -> { SCREEN_ON_LYRICS -> {
keepScreenOn(bottomSheetBehavior.state == STATE_EXPANDED && PreferenceUtil.lyricsScreenOn && PreferenceUtil.showLyrics || PreferenceUtil.isScreenOnEnabled) keepScreenOn(bottomSheetBehavior.state == STATE_EXPANDED && PreferenceUtil.lyricsScreenOn && PreferenceUtil.showLyrics || PreferenceUtil.isScreenOnEnabled)

View file

@ -31,7 +31,6 @@ import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.maybeShowAnnoyingToasts import code.name.monkey.retromusic.util.maybeShowAnnoyingToasts
import code.name.monkey.retromusic.util.theme.getNightMode import code.name.monkey.retromusic.util.theme.getNightMode
import code.name.monkey.retromusic.util.theme.getThemeResValue import code.name.monkey.retromusic.util.theme.getThemeResValue
import com.google.android.play.core.splitcompat.SplitCompat
import java.util.* import java.util.*
abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable { abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable {
@ -105,6 +104,6 @@ abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable {
Locale.forLanguageTag(code) Locale.forLanguageTag(code)
} }
super.attachBaseContext(LanguageContextWrapper.wrap(newBase, locale)) super.attachBaseContext(LanguageContextWrapper.wrap(newBase, locale))
SplitCompat.install(this) installSplitCompat()
} }
} }

View file

@ -42,7 +42,7 @@ class PlayingQueueAdapter(
activity: FragmentActivity, activity: FragmentActivity,
dataSet: MutableList<Song>, dataSet: MutableList<Song>,
private var current: Int, private var current: Int,
itemLayoutRes: Int itemLayoutRes: Int,
) : SongAdapter( ) : SongAdapter(
activity, dataSet, itemLayoutRes, null activity, dataSet, itemLayoutRes, null
), DraggableItemAdapter<PlayingQueueAdapter.ViewHolder>, ), DraggableItemAdapter<PlayingQueueAdapter.ViewHolder>,
@ -153,6 +153,14 @@ class PlayingQueueAdapter(
dragView?.isVisible = true dragView?.isVisible = true
} }
override fun onClick(v: View?) {
if (isInQuickSelectMode) {
toggleChecked(layoutPosition)
} else {
MusicPlayerRemote.playSongAt(layoutPosition)
}
}
override fun onSongMenuItemClick(item: MenuItem): Boolean { override fun onSongMenuItemClick(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.action_remove_from_playing_queue -> { R.id.action_remove_from_playing_queue -> {
@ -209,7 +217,7 @@ class PlayingQueueAdapter(
internal class SwipedResultActionRemoveItem( internal class SwipedResultActionRemoveItem(
private val adapter: PlayingQueueAdapter, private val adapter: PlayingQueueAdapter,
private val position: Int, private val position: Int,
private val activity: FragmentActivity private val activity: FragmentActivity,
) : SwipeResultActionRemoveItem() { ) : SwipeResultActionRemoveItem() {
private var songToRemove: Song? = null private var songToRemove: Song? = null

View file

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

View file

@ -95,7 +95,7 @@ class SleepTimerDialog : DialogFragment() {
shouldFinishLastSong.isVisible = false shouldFinishLastSong.isVisible = false
timerUpdater.start() timerUpdater.start()
setPositiveButton(android.R.string.ok, null) setPositiveButton(android.R.string.ok, null)
setNegativeButton(R.string.cast_stop) { _, _ -> setNegativeButton(R.string.action_cancel) { _, _ ->
timerUpdater.cancel() timerUpdater.cancel()
val previous = makeTimerPendingIntent(PendingIntent.FLAG_NO_CREATE) val previous = makeTimerPendingIntent(PendingIntent.FLAG_NO_CREATE)
if (previous != null) { if (previous != null) {

View file

@ -14,9 +14,11 @@
*/ */
package code.name.monkey.retromusic.extensions package code.name.monkey.retromusic.extensions
import android.R
import android.animation.Animator import android.animation.Animator
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.content.res.ColorStateList
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -25,6 +27,7 @@ import android.view.ViewTreeObserver
import android.view.animation.AnimationUtils import android.view.animation.AnimationUtils
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.EditText import android.widget.EditText
import androidx.annotation.ColorInt
import androidx.annotation.LayoutRes import androidx.annotation.LayoutRes
import androidx.annotation.Px import androidx.annotation.Px
import androidx.core.animation.doOnEnd import androidx.core.animation.doOnEnd
@ -67,6 +70,15 @@ fun EditText.appHandleColor(): EditText {
return this return this
} }
fun NavigationBarView.setItemColors(@ColorInt normalColor: Int, @ColorInt selectedColor: Int) {
val csl = ColorStateList(
arrayOf(intArrayOf(-R.attr.state_checked), intArrayOf(R.attr.state_checked)),
intArrayOf(normalColor, selectedColor)
)
itemIconTintList = csl
itemTextColor = csl
}
/** /**
* Potentially animate showing a [BottomNavigationView]. * Potentially animate showing a [BottomNavigationView].
* *

View file

@ -24,6 +24,7 @@ import androidx.recyclerview.widget.GridLayoutManager
import code.name.monkey.retromusic.EXTRA_ALBUM_ID import code.name.monkey.retromusic.EXTRA_ALBUM_ID
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.album.AlbumAdapter import code.name.monkey.retromusic.adapter.album.AlbumAdapter
import code.name.monkey.retromusic.extensions.setUpMediaRouteButton
import code.name.monkey.retromusic.extensions.surfaceColor import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.fragments.GridStyle import code.name.monkey.retromusic.fragments.GridStyle
import code.name.monkey.retromusic.fragments.ReloadType import code.name.monkey.retromusic.fragments.ReloadType
@ -41,7 +42,6 @@ import com.afollestad.materialcab.attached.AttachedCab
import com.afollestad.materialcab.attached.destroy import com.afollestad.materialcab.attached.destroy
import com.afollestad.materialcab.attached.isActive import com.afollestad.materialcab.attached.isActive
import com.afollestad.materialcab.createCab import com.afollestad.materialcab.createCab
import com.google.android.gms.cast.framework.CastButtonFactory
class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridLayoutManager>(), class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridLayoutManager>(),
IAlbumClickListener, ICabHolder { IAlbumClickListener, ICabHolder {
@ -169,7 +169,7 @@ class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridL
setupLayoutMenu(layoutItem.subMenu) setupLayoutMenu(layoutItem.subMenu)
setUpSortOrderMenu(menu.findItem(R.id.action_sort_order).subMenu) setUpSortOrderMenu(menu.findItem(R.id.action_sort_order).subMenu)
//Setting up cast button //Setting up cast button
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.action_cast) requireContext().setUpMediaRouteButton(menu)
} }
private fun setUpSortOrderMenu( private fun setUpSortOrderMenu(

View file

@ -25,6 +25,7 @@ import code.name.monkey.retromusic.EXTRA_ARTIST_ID
import code.name.monkey.retromusic.EXTRA_ARTIST_NAME import code.name.monkey.retromusic.EXTRA_ARTIST_NAME
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.artist.ArtistAdapter import code.name.monkey.retromusic.adapter.artist.ArtistAdapter
import code.name.monkey.retromusic.extensions.setUpMediaRouteButton
import code.name.monkey.retromusic.extensions.surfaceColor import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.fragments.GridStyle import code.name.monkey.retromusic.fragments.GridStyle
import code.name.monkey.retromusic.fragments.ReloadType import code.name.monkey.retromusic.fragments.ReloadType
@ -43,7 +44,6 @@ import com.afollestad.materialcab.attached.AttachedCab
import com.afollestad.materialcab.attached.destroy import com.afollestad.materialcab.attached.destroy
import com.afollestad.materialcab.attached.isActive import com.afollestad.materialcab.attached.isActive
import com.afollestad.materialcab.createCab import com.afollestad.materialcab.createCab
import com.google.android.gms.cast.framework.CastButtonFactory
class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment<ArtistAdapter, GridLayoutManager>(), class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment<ArtistAdapter, GridLayoutManager>(),
IArtistClickListener, IAlbumArtistClickListener, ICabHolder { IArtistClickListener, IAlbumArtistClickListener, ICabHolder {
@ -180,7 +180,7 @@ class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment<ArtistAdapter, Gri
setUpSortOrderMenu(menu.findItem(R.id.action_sort_order).subMenu) setUpSortOrderMenu(menu.findItem(R.id.action_sort_order).subMenu)
setupAlbumArtistMenu(menu) setupAlbumArtistMenu(menu)
//Setting up cast button //Setting up cast button
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.action_cast) requireContext().setUpMediaRouteButton(menu)
} }
private fun setupAlbumArtistMenu(menu: Menu) { private fun setupAlbumArtistMenu(menu: Menu) {

View file

@ -82,9 +82,8 @@ abstract class AbsPlayerControlsFragment(@LayoutRes layout: Int) : AbsMusicServi
if (seekBar == null) { if (seekBar == null) {
progressSlider?.valueTo = total.toFloat() progressSlider?.valueTo = total.toFloat()
if (progress > total) return progressSlider?.value =
progressSlider?.value = progress.toFloat() progress.toFloat().coerceIn(progressSlider?.valueFrom, progressSlider?.valueTo)
} else { } else {
seekBar?.max = total seekBar?.max = total

View file

@ -26,12 +26,12 @@ import androidx.recyclerview.widget.LinearLayoutManager
import code.name.monkey.retromusic.EXTRA_GENRE import code.name.monkey.retromusic.EXTRA_GENRE
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.GenreAdapter import code.name.monkey.retromusic.adapter.GenreAdapter
import code.name.monkey.retromusic.extensions.setUpMediaRouteButton
import code.name.monkey.retromusic.fragments.ReloadType import code.name.monkey.retromusic.fragments.ReloadType
import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewFragment import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewFragment
import code.name.monkey.retromusic.interfaces.IGenreClickListener import code.name.monkey.retromusic.interfaces.IGenreClickListener
import code.name.monkey.retromusic.model.Genre import code.name.monkey.retromusic.model.Genre
import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil
import com.google.android.gms.cast.framework.CastButtonFactory
import com.google.android.material.transition.MaterialSharedAxis import com.google.android.material.transition.MaterialSharedAxis
class class
@ -67,7 +67,7 @@ GenresFragment : AbsRecyclerViewFragment<GenreAdapter, LinearLayoutManager>(),
menu.removeItem(R.id.action_sort_order) menu.removeItem(R.id.action_sort_order)
menu.findItem(R.id.action_settings).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM) menu.findItem(R.id.action_settings).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
//Setting up cast button //Setting up cast button
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.action_cast) requireContext().setUpMediaRouteButton(menu)
} }
override fun onResume() { override fun onResume() {

View file

@ -35,10 +35,7 @@ import code.name.monkey.retromusic.adapter.HomeAdapter
import code.name.monkey.retromusic.databinding.FragmentHomeBinding import code.name.monkey.retromusic.databinding.FragmentHomeBinding
import code.name.monkey.retromusic.dialogs.CreatePlaylistDialog import code.name.monkey.retromusic.dialogs.CreatePlaylistDialog
import code.name.monkey.retromusic.dialogs.ImportPlaylistDialog import code.name.monkey.retromusic.dialogs.ImportPlaylistDialog
import code.name.monkey.retromusic.extensions.accentColor import code.name.monkey.retromusic.extensions.*
import code.name.monkey.retromusic.extensions.dip
import code.name.monkey.retromusic.extensions.drawNextToNavbar
import code.name.monkey.retromusic.extensions.elevatedAccentColor
import code.name.monkey.retromusic.fragments.ReloadType import code.name.monkey.retromusic.fragments.ReloadType
import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import code.name.monkey.retromusic.glide.GlideApp import code.name.monkey.retromusic.glide.GlideApp
@ -48,7 +45,6 @@ import code.name.monkey.retromusic.interfaces.IScrollHelper
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.PreferenceUtil.userName import code.name.monkey.retromusic.util.PreferenceUtil.userName
import com.google.android.gms.cast.framework.CastButtonFactory
import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.transition.MaterialFadeThrough import com.google.android.material.transition.MaterialFadeThrough
import com.google.android.material.transition.MaterialSharedAxis import com.google.android.material.transition.MaterialSharedAxis
@ -212,7 +208,7 @@ class HomeFragment :
ATHToolbarActivity.getToolbarBackgroundColor(binding.toolbar) ATHToolbarActivity.getToolbarBackgroundColor(binding.toolbar)
) )
//Setting up cast button //Setting up cast button
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.action_cast) requireContext().setUpMediaRouteButton(menu)
} }
override fun scrollToTop() { override fun scrollToTop() {

View file

@ -30,11 +30,11 @@ import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentLibraryBinding import code.name.monkey.retromusic.databinding.FragmentLibraryBinding
import code.name.monkey.retromusic.dialogs.CreatePlaylistDialog import code.name.monkey.retromusic.dialogs.CreatePlaylistDialog
import code.name.monkey.retromusic.dialogs.ImportPlaylistDialog import code.name.monkey.retromusic.dialogs.ImportPlaylistDialog
import code.name.monkey.retromusic.extensions.setUpMediaRouteButton
import code.name.monkey.retromusic.extensions.whichFragment import code.name.monkey.retromusic.extensions.whichFragment
import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import code.name.monkey.retromusic.model.CategoryInfo import code.name.monkey.retromusic.model.CategoryInfo
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import com.google.android.gms.cast.framework.CastButtonFactory
class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) { class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) {
@ -99,7 +99,7 @@ class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) {
getToolbarBackgroundColor(binding.toolbar) getToolbarBackgroundColor(binding.toolbar)
) )
//Setting up cast button //Setting up cast button
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.action_cast) requireContext().setUpMediaRouteButton(menu)
} }
override fun onMenuItemSelected(item: MenuItem): Boolean { override fun onMenuItemSelected(item: MenuItem): Boolean {

View file

@ -12,11 +12,10 @@
* See the GNU General Public License for more details. * See the GNU General Public License for more details.
* *
*/ */
package code.name.monkey.retromusic.fragments.other package code.name.monkey.retromusic.fragments.lyrics
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.provider.MediaStore import android.provider.MediaStore
@ -26,23 +25,19 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.IntentSenderRequest import androidx.activity.result.IntentSenderRequest
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.transition.Fade import androidx.transition.Fade
import androidx.viewpager2.adapter.FragmentStateAdapter
import code.name.monkey.appthemehelper.common.ATHToolbarActivity import code.name.monkey.appthemehelper.common.ATHToolbarActivity
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.appthemehelper.util.VersionUtils import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.activities.tageditor.TagWriter import code.name.monkey.retromusic.activities.tageditor.TagWriter
import code.name.monkey.retromusic.databinding.FragmentLyricsBinding import code.name.monkey.retromusic.databinding.FragmentLyricsBinding
import code.name.monkey.retromusic.databinding.FragmentNormalLyricsBinding import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.databinding.FragmentSyncedLyricsBinding import code.name.monkey.retromusic.extensions.materialDialog
import code.name.monkey.retromusic.extensions.* import code.name.monkey.retromusic.extensions.openUrl
import code.name.monkey.retromusic.extensions.uri
import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.lyrics.LrcView import code.name.monkey.retromusic.lyrics.LrcView
@ -52,7 +47,6 @@ import code.name.monkey.retromusic.util.FileUtils
import code.name.monkey.retromusic.util.LyricUtil import code.name.monkey.retromusic.util.LyricUtil
import code.name.monkey.retromusic.util.UriUtil import code.name.monkey.retromusic.util.UriUtil
import com.afollestad.materialdialogs.input.input import com.afollestad.materialdialogs.input.input
import com.google.android.material.tabs.TabLayoutMediator
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jaudiotagger.audio.AudioFileIO import org.jaudiotagger.audio.AudioFileIO
@ -60,14 +54,23 @@ import org.jaudiotagger.tag.FieldKey
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.util.* import java.util.*
import kotlin.collections.set
class LyricsFragment : AbsMainActivityFragment(R.layout.fragment_lyrics) { class LyricsFragment : AbsMainActivityFragment(R.layout.fragment_lyrics),
MusicProgressViewUpdateHelper.Callback {
private var _binding: FragmentLyricsBinding? = null private var _binding: FragmentLyricsBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
private lateinit var song: Song private lateinit var song: Song
private lateinit var lyricsSectionsAdapter: LyricsSectionsAdapter private lateinit var normalLyricsLauncher: ActivityResultLauncher<IntentSenderRequest>
private lateinit var editSyncedLyricsLauncher: ActivityResultLauncher<IntentSenderRequest>
private lateinit var cacheFile: File
private var syncedLyrics: String = ""
private lateinit var syncedFileUri: Uri
private var lyricsType: LyricsType = LyricsType.NORMAL_LYRICS
private val googleSearchLrcUrl: String private val googleSearchLrcUrl: String
get() { get() {
@ -77,22 +80,8 @@ class LyricsFragment : AbsMainActivityFragment(R.layout.fragment_lyrics) {
baseUrl += query baseUrl += query
return baseUrl return baseUrl
} }
private val syairSearchLrcUrl: String
get() {
var baseUrl = "https://www.syair.info/search?"
var query = song.title + "+" + song.artistName
query = "q=" + query.replace(" ", "+")
baseUrl += query
return baseUrl
}
private lateinit var normalLyricsLauncher: ActivityResultLauncher<IntentSenderRequest> private lateinit var updateHelper: MusicProgressViewUpdateHelper
private lateinit var newSyncedLyricsLauncher: ActivityResultLauncher<Intent>
private lateinit var editSyncedLyricsLauncher: ActivityResultLauncher<IntentSenderRequest>
private lateinit var cacheFile: File
private var syncedLyrics: String = ""
private lateinit var syncedFileUri: Uri
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -103,14 +92,6 @@ class LyricsFragment : AbsMainActivityFragment(R.layout.fragment_lyrics) {
FileUtils.copyFileToUri(requireContext(), cacheFile, song.uri) FileUtils.copyFileToUri(requireContext(), cacheFile, song.uri)
} }
} }
newSyncedLyricsLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
context?.contentResolver?.openOutputStream(result.data?.data!!)?.use {
it.write(syncedLyrics.toByteArray())
}
}
}
editSyncedLyricsLauncher = editSyncedLyricsLauncher =
registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
if (it.resultCode == Activity.RESULT_OK) { if (it.resultCode == Activity.RESULT_OK) {
@ -127,36 +108,42 @@ class LyricsFragment : AbsMainActivityFragment(R.layout.fragment_lyrics) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
enterTransition = Fade() enterTransition = Fade()
exitTransition = Fade() exitTransition = Fade()
updateTitleSong()
lyricsSectionsAdapter = LyricsSectionsAdapter(requireActivity())
_binding = FragmentLyricsBinding.bind(view) _binding = FragmentLyricsBinding.bind(view)
binding.container.transitionName = "lyrics" updateHelper = MusicProgressViewUpdateHelper(this, 500, 1000)
updateTitleSong()
setupLyricsView()
loadLyrics()
setupWakelock() setupWakelock()
setupViews() setupViews()
setupToolbar() setupToolbar()
} }
private fun setupViews() { private fun setupLyricsView() {
binding.lyricsPager.adapter = lyricsSectionsAdapter binding.lyricsView.apply {
TabLayoutMediator(binding.tabLyrics, binding.lyricsPager) { tab, position -> setCurrentColor(accentColor())
tab.text = when (position) { setTimeTextColor(accentColor())
0 -> getString(R.string.synced_lyrics) setTimelineColor(accentColor())
1 -> getString(R.string.normal_lyrics) setTimelineTextColor(accentColor())
else -> "" setDraggable(true, LrcView.OnPlayClickListener {
MusicPlayerRemote.seekTo(it.toInt())
return@OnPlayClickListener true
})
}
} }
}.attach()
// lyricsPager.isUserInputEnabled = false
binding.tabLyrics.setSelectedTabIndicatorColor(accentColor()) override fun onUpdateProgressViews(progress: Int, total: Int) {
binding.tabLyrics.setTabTextColors(textColorSecondary(), accentColor()) binding.lyricsView.updateTime(progress.toLong())
}
private fun setupViews() {
binding.editButton.accentColor() binding.editButton.accentColor()
binding.editButton.setOnClickListener { binding.editButton.setOnClickListener {
when (binding.lyricsPager.currentItem) { when (lyricsType) {
0 -> { LyricsType.SYNCED_LYRICS -> {
editSyncedLyrics() editSyncedLyrics()
} }
1 -> { LyricsType.NORMAL_LYRICS -> {
editNormalLyrics() editNormalLyrics()
} }
} }
@ -166,11 +153,13 @@ class LyricsFragment : AbsMainActivityFragment(R.layout.fragment_lyrics) {
override fun onPlayingMetaChanged() { override fun onPlayingMetaChanged() {
super.onPlayingMetaChanged() super.onPlayingMetaChanged()
updateTitleSong() updateTitleSong()
loadLyrics()
} }
override fun onServiceConnected() { override fun onServiceConnected() {
super.onServiceConnected() super.onServiceConnected()
updateTitleSong() updateTitleSong()
loadLyrics()
} }
private fun updateTitleSong() { private fun updateTitleSong() {
@ -190,7 +179,7 @@ class LyricsFragment : AbsMainActivityFragment(R.layout.fragment_lyrics) {
} }
override fun onCreateMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_search, menu) inflater.inflate(R.menu.menu_lyrics, menu)
ToolbarContentTintHelper.handleOnCreateOptionsMenu( ToolbarContentTintHelper.handleOnCreateOptionsMenu(
requireContext(), requireContext(),
binding.toolbar, binding.toolbar,
@ -201,27 +190,23 @@ class LyricsFragment : AbsMainActivityFragment(R.layout.fragment_lyrics) {
override fun onMenuItemSelected(item: MenuItem): Boolean { override fun onMenuItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.action_search) { if (item.itemId == R.id.action_search) {
openUrl(when (binding.lyricsPager.currentItem) { openUrl(googleSearchLrcUrl)
0 -> syairSearchLrcUrl
1 -> googleSearchLrcUrl
else -> googleSearchLrcUrl
}
)
} }
return false return false
} }
@SuppressLint("CheckResult") @SuppressLint("CheckResult")
private fun editNormalLyrics() { private fun editNormalLyrics(lyrics: String? = null) {
var content = "" val file = File(song.data)
val file = File(MusicPlayerRemote.currentSong.data) val content = lyrics ?: try {
try { AudioFileIO.read(file).tagOrCreateDefault.getFirst(FieldKey.LYRICS)
content = AudioFileIO.read(file).tagOrCreateDefault.getFirst(FieldKey.LYRICS)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
""
} }
val song = song
materialDialog().show { materialDialog().show {
title(res = R.string.edit_normal_lyrics) title(res = R.string.edit_normal_lyrics)
input( input(
@ -231,7 +216,6 @@ class LyricsFragment : AbsMainActivityFragment(R.layout.fragment_lyrics) {
) { _, input -> ) { _, input ->
val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java) val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java)
fieldKeyValueMap[FieldKey.LYRICS] = input.toString() fieldKeyValueMap[FieldKey.LYRICS] = input.toString()
syncedLyrics = input.toString()
GlobalScope.launch { GlobalScope.launch {
if (VersionUtils.hasR()) { if (VersionUtils.hasR()) {
cacheFile = TagWriter.writeTagsToFilesR( cacheFile = TagWriter.writeTagsToFilesR(
@ -258,7 +242,7 @@ class LyricsFragment : AbsMainActivityFragment(R.layout.fragment_lyrics) {
} }
} }
positiveButton(res = R.string.save) { positiveButton(res = R.string.save) {
(lyricsSectionsAdapter.fragments[1].first as NormalLyrics).loadNormalLyrics() loadNormalLyrics()
} }
negativeButton(res = android.R.string.cancel) negativeButton(res = android.R.string.cancel)
} }
@ -266,9 +250,10 @@ class LyricsFragment : AbsMainActivityFragment(R.layout.fragment_lyrics) {
@SuppressLint("CheckResult") @SuppressLint("CheckResult")
private fun editSyncedLyrics() { private fun editSyncedLyrics(lyrics: String? = null) {
val content: String = LyricUtil.getStringFromLrc(LyricUtil.getSyncedLyricsFile(song)) val content = lyrics ?: LyricUtil.getStringFromLrc(LyricUtil.getSyncedLyricsFile(song))
val song = song
materialDialog().show { materialDialog().show {
title(res = R.string.edit_synced_lyrics) title(res = R.string.edit_synced_lyrics)
input( input(
@ -291,57 +276,37 @@ class LyricsFragment : AbsMainActivityFragment(R.layout.fragment_lyrics) {
IntentSenderRequest.Builder(pendingIntent).build() IntentSenderRequest.Builder(pendingIntent).build()
) )
} else { } else {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java)
intent.addCategory(Intent.CATEGORY_OPENABLE) fieldKeyValueMap[FieldKey.LYRICS] = input.toString()
intent.type = "*/*" GlobalScope.launch {
intent.putExtra( cacheFile = TagWriter.writeTagsToFilesR(
Intent.EXTRA_TITLE, requireContext(),
LyricUtil.getLrcOriginalPath(File(song.data).name) AudioTagInfo(listOf(song.data), fieldKeyValueMap, null)
)[0]
val pendingIntent = MediaStore.createWriteRequest(
requireContext().contentResolver,
listOf(song.uri)
) )
newSyncedLyricsLauncher.launch(intent)
normalLyricsLauncher.launch(
IntentSenderRequest.Builder(pendingIntent).build()
)
}
} }
} else { } else {
LyricUtil.writeLrc(song, input.toString()) LyricUtil.writeLrc(song, input.toString())
} }
} }
positiveButton(res = R.string.save) { positiveButton(res = R.string.save) {
(lyricsSectionsAdapter.fragments[0].first as SyncedLyrics).loadLRCLyrics() loadLRCLyrics()
} }
negativeButton(res = android.R.string.cancel) negativeButton(res = android.R.string.cancel)
} }
} }
class LyricsSectionsAdapter(fragmentActivity: FragmentActivity) : private fun loadNormalLyrics() {
FragmentStateAdapter(fragmentActivity) {
val fragments = listOf(
Pair(SyncedLyrics(), R.string.synced_lyrics),
Pair(NormalLyrics(), R.string.normal_lyrics)
)
override fun getItemCount(): Int {
return fragments.size
}
override fun createFragment(position: Int): Fragment {
return fragments[position].first
}
}
class NormalLyrics : AbsMusicServiceFragment(R.layout.fragment_normal_lyrics) {
private var _binding: FragmentNormalLyricsBinding? = null
private val binding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
_binding = FragmentNormalLyricsBinding.bind(view)
loadNormalLyrics()
super.onViewCreated(view, savedInstanceState)
}
fun loadNormalLyrics() {
var lyrics: String? = null var lyrics: String? = null
val file = File(MusicPlayerRemote.currentSong.data) val file = File(song.data)
try { try {
lyrics = AudioFileIO.read(file).tagOrCreateDefault.getFirst(FieldKey.LYRICS) lyrics = AudioFileIO.read(file).tagOrCreateDefault.getFirst(FieldKey.LYRICS)
} catch (e: Exception) { } catch (e: Exception) {
@ -351,71 +316,38 @@ class LyricsFragment : AbsMainActivityFragment(R.layout.fragment_lyrics) {
binding.normalLyrics.text = lyrics binding.normalLyrics.text = lyrics
} }
override fun onPlayingMetaChanged() { /**
super.onPlayingMetaChanged() * @return success
loadNormalLyrics() */
} private fun loadLRCLyrics(): Boolean {
override fun onServiceConnected() {
super.onServiceConnected()
loadNormalLyrics()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
class SyncedLyrics : AbsMusicServiceFragment(R.layout.fragment_synced_lyrics),
MusicProgressViewUpdateHelper.Callback {
private var _binding: FragmentSyncedLyricsBinding? = null
private val binding get() = _binding!!
private lateinit var updateHelper: MusicProgressViewUpdateHelper
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
updateHelper = MusicProgressViewUpdateHelper(this, 500, 1000)
_binding = FragmentSyncedLyricsBinding.bind(view)
setupLyricsView()
loadLRCLyrics()
super.onViewCreated(view, savedInstanceState)
}
fun loadLRCLyrics() {
binding.lyricsView.setLabel(getString(R.string.empty)) binding.lyricsView.setLabel(getString(R.string.empty))
LyricUtil.getSyncedLyricsFile(MusicPlayerRemote.currentSong)?.let { val lrcFile = LyricUtil.getSyncedLyricsFile(song)
binding.lyricsView.loadLrc(it) if (lrcFile != null) {
binding.lyricsView.loadLrc(lrcFile)
println("File: ${lrcFile.absolutePath}")
} else {
val embeddedLyrics = LyricUtil.getEmbeddedSyncedLyrics(song.data)
if (embeddedLyrics != null) {
binding.lyricsView.loadLrc(embeddedLyrics)
println("Lyrics: ${embeddedLyrics.substring(0..50)}")
} else {
return false
} }
} }
return true
}
private fun setupLyricsView() { private fun loadLyrics() {
binding.lyricsView.apply { lyricsType = if (!loadLRCLyrics()) {
setCurrentColor(accentColor()) loadNormalLyrics()
setTimeTextColor(accentColor()) LyricsType.SYNCED_LYRICS
setTimelineColor(accentColor()) } else {
setTimelineTextColor(accentColor()) binding.noLyricsFound.isVisible = false
setDraggable(true, LrcView.OnPlayClickListener { binding.normalLyrics.text = ""
MusicPlayerRemote.seekTo(it.toInt()) LyricsType.NORMAL_LYRICS
return@OnPlayClickListener true
})
} }
} }
override fun onUpdateProgressViews(progress: Int, total: Int) {
binding.lyricsView.updateTime(progress.toLong())
}
override fun onPlayingMetaChanged() {
super.onPlayingMetaChanged()
loadLRCLyrics()
}
override fun onServiceConnected() {
super.onServiceConnected()
loadLRCLyrics()
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
updateHelper.start() updateHelper.start()
@ -428,14 +360,13 @@ class LyricsFragment : AbsMainActivityFragment(R.layout.fragment_lyrics) {
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
if (MusicPlayerRemote.playingQueue.isNotEmpty())
mainActivity.expandPanel()
_binding = null _binding = null
} }
}
override fun onDestroyView() { enum class LyricsType {
super.onDestroyView() NORMAL_LYRICS,
if (MusicPlayerRemote.playingQueue.isNotEmpty()) SYNCED_LYRICS
(requireActivity() as MainActivity).expandPanel()
_binding = null
} }
} }

View file

@ -14,7 +14,6 @@
*/ */
package code.name.monkey.retromusic.fragments.other package code.name.monkey.retromusic.fragments.other
import android.animation.ObjectAnimator
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
@ -23,7 +22,6 @@ import android.text.style.ForegroundColorSpan
import android.view.GestureDetector import android.view.GestureDetector
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.animation.DecelerateInterpolator
import androidx.core.text.toSpannable import androidx.core.text.toSpannable
import androidx.core.view.isVisible import androidx.core.view.isVisible
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
@ -139,10 +137,7 @@ open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_p
override fun onUpdateProgressViews(progress: Int, total: Int) { override fun onUpdateProgressViews(progress: Int, total: Int) {
binding.progressBar.max = total binding.progressBar.max = total
val animator = ObjectAnimator.ofInt(binding.progressBar, "progress", progress) binding.progressBar.progress = progress
animator.duration = 1000
animator.interpolator = DecelerateInterpolator()
animator.start()
} }
override fun onResume() { override fun onResume() {

View file

@ -11,6 +11,7 @@ import androidx.preference.PreferenceManager
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.SHOW_LYRICS import code.name.monkey.retromusic.SHOW_LYRICS
import code.name.monkey.retromusic.databinding.FragmentCoverLyricsBinding import code.name.monkey.retromusic.databinding.FragmentCoverLyricsBinding
import code.name.monkey.retromusic.extensions.dipToPix
import code.name.monkey.retromusic.fragments.NowPlayingScreen import code.name.monkey.retromusic.fragments.NowPlayingScreen
import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
@ -21,6 +22,7 @@ import code.name.monkey.retromusic.model.lyrics.AbsSynchronizedLyrics
import code.name.monkey.retromusic.model.lyrics.Lyrics import code.name.monkey.retromusic.model.lyrics.Lyrics
import code.name.monkey.retromusic.util.LyricUtil import code.name.monkey.retromusic.util.LyricUtil
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jaudiotagger.audio.exceptions.CannotReadException import org.jaudiotagger.audio.exceptions.CannotReadException
@ -56,6 +58,17 @@ class CoverLyricsFragment : AbsMusicServiceFragment(R.layout.fragment_cover_lyri
} }
} }
fun setColors(color: MediaNotificationProcessor) {
binding.run {
playerLyrics.background = null
playerLyricsLine1.setTextColor(color.primaryTextColor)
playerLyricsLine1.setShadowLayer(dipToPix(10f), 0f, 0f, color.backgroundColor)
playerLyricsLine2.setTextColor(color.primaryTextColor)
playerLyricsLine2.setShadowLayer(dipToPix(10f), 0f, 0f, color.backgroundColor)
}
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (key == SHOW_LYRICS) { if (key == SHOW_LYRICS) {
if (sharedPreferences?.getBoolean(key, false) == true) { if (sharedPreferences?.getBoolean(key, false) == true) {

View file

@ -46,10 +46,9 @@ import code.name.monkey.retromusic.model.lyrics.Lyrics
import code.name.monkey.retromusic.transform.CarousalPagerTransformer import code.name.monkey.retromusic.transform.CarousalPagerTransformer
import code.name.monkey.retromusic.transform.ParallaxPagerTransformer import code.name.monkey.retromusic.transform.ParallaxPagerTransformer
import code.name.monkey.retromusic.util.LyricUtil import code.name.monkey.retromusic.util.LyricUtil
import code.name.monkey.retromusic.util.LyricsType import code.name.monkey.retromusic.util.CoverLyricsType
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import code.name.monkey.retromusic.util.logD
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -86,21 +85,19 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
} }
private fun updateLyrics() { private fun updateLyrics() {
binding.lyricsView.setLabel(context?.getString(R.string.no_lyrics_found))
val song = MusicPlayerRemote.currentSong val song = MusicPlayerRemote.currentSong
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
val lrcFile = LyricUtil.getSyncedLyricsFile(song) val lrcFile = LyricUtil.getSyncedLyricsFile(song)
if (lrcFile != null) { if (lrcFile != null) {
withContext(Dispatchers.Main) {
binding.lyricsView.loadLrc(lrcFile) binding.lyricsView.loadLrc(lrcFile)
}
} else { } else {
val embeddedLyrics = LyricUtil.getEmbeddedSyncedLyrics(song.data) val embeddedLyrics = LyricUtil.getEmbeddedSyncedLyrics(song.data)
withContext(Dispatchers.Main) {
if (embeddedLyrics != null) { if (embeddedLyrics != null) {
binding.lyricsView.loadLrc(embeddedLyrics) binding.lyricsView.loadLrc(embeddedLyrics)
} else { } else {
withContext(Dispatchers.Main) {
binding.lyricsView.reset() binding.lyricsView.reset()
binding.lyricsView.setLabel(context?.getString(R.string.no_lyrics_found))
} }
} }
} }
@ -176,13 +173,11 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
} }
override fun onServiceConnected() { override fun onServiceConnected() {
logD("Service Connected")
updatePlayingQueue() updatePlayingQueue()
updateLyrics() updateLyrics()
} }
override fun onPlayingMetaChanged() { override fun onPlayingMetaChanged() {
logD("Playing Meta Changed")
if (viewPager.currentItem != MusicPlayerRemote.position) { if (viewPager.currentItem != MusicPlayerRemote.position) {
viewPager.setCurrentItem(MusicPlayerRemote.position, true) viewPager.setCurrentItem(MusicPlayerRemote.position, true)
} }
@ -190,7 +185,6 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
} }
override fun onQueueChanged() { override fun onQueueChanged() {
logD("Queue Changed")
updatePlayingQueue() updatePlayingQueue()
} }
@ -224,7 +218,7 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
binding.coverLyrics.isVisible = false binding.coverLyrics.isVisible = false
binding.lyricsView.isVisible = false binding.lyricsView.isVisible = false
binding.viewPager.isVisible = true binding.viewPager.isVisible = true
val lyrics: View = if (PreferenceUtil.lyricsType == LyricsType.REPLACE_COVER) { val lyrics: View = if (PreferenceUtil.lyricsType == CoverLyricsType.REPLACE_COVER) {
ObjectAnimator.ofFloat(viewPager, View.ALPHA, if (visible) 0F else 1F).start() ObjectAnimator.ofFloat(viewPager, View.ALPHA, if (visible) 0F else 1F).start()
lrcView lrcView
} else { } else {
@ -244,7 +238,7 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
// Don't show lyrics container for below conditions // Don't show lyrics container for below conditions
if (lyricViewNpsList.contains(nps) && PreferenceUtil.showLyrics) { if (lyricViewNpsList.contains(nps) && PreferenceUtil.showLyrics) {
showLyrics(true) showLyrics(true)
if (PreferenceUtil.lyricsType == LyricsType.REPLACE_COVER) { if (PreferenceUtil.lyricsType == CoverLyricsType.REPLACE_COVER) {
progressViewUpdateHelper?.start() progressViewUpdateHelper?.start()
} }
} else { } else {
@ -255,8 +249,7 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
private fun updatePlayingQueue() { private fun updatePlayingQueue() {
binding.viewPager.apply { binding.viewPager.apply {
adapter = AlbumCoverPagerAdapter(childFragmentManager, MusicPlayerRemote.playingQueue) adapter = AlbumCoverPagerAdapter(parentFragmentManager, MusicPlayerRemote.playingQueue)
adapter?.notifyDataSetChanged()
setCurrentItem(MusicPlayerRemote.position, true) setCurrentItem(MusicPlayerRemote.position, true)
onPageSelected(MusicPlayerRemote.position) onPageSelected(MusicPlayerRemote.position)
} }
@ -265,7 +258,6 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
logD("Page Selected $position")
currentPosition = position currentPosition = position
if (binding.viewPager.adapter != null) { if (binding.viewPager.adapter != null) {
(binding.viewPager.adapter as AlbumCoverPagerAdapter).receiveColor( (binding.viewPager.adapter as AlbumCoverPagerAdapter).receiveColor(

View file

@ -74,8 +74,8 @@ class CardBlurFragment : AbsPlayerFragment(R.layout.fragment_card_blur_player),
libraryViewModel.updateColor(color.backgroundColor) libraryViewModel.updateColor(color.backgroundColor)
ToolbarContentTintHelper.colorizeToolbar(binding.playerToolbar, Color.WHITE, activity) ToolbarContentTintHelper.colorizeToolbar(binding.playerToolbar, Color.WHITE, activity)
binding.playerToolbar.setTitleTextColor(Color.WHITE) binding.title.setTextColor(Color.WHITE)
binding.playerToolbar.setSubtitleTextColor(Color.WHITE) binding.text.setTextColor(Color.WHITE)
} }
override fun toggleFavorite(song: Song) { override fun toggleFavorite(song: Song) {
@ -94,7 +94,7 @@ class CardBlurFragment : AbsPlayerFragment(R.layout.fragment_card_blur_player),
_binding = FragmentCardBlurPlayerBinding.bind(view) _binding = FragmentCardBlurPlayerBinding.bind(view)
setUpSubFragments() setUpSubFragments()
setUpPlayerToolbar() setUpPlayerToolbar()
binding.cardContainer?.drawAboveSystemBars() binding.playerToolbar.drawAboveSystemBars()
} }
private fun setUpSubFragments() { private fun setUpSubFragments() {
@ -130,9 +130,9 @@ class CardBlurFragment : AbsPlayerFragment(R.layout.fragment_card_blur_player),
private fun updateSong() { private fun updateSong() {
val song = MusicPlayerRemote.currentSong val song = MusicPlayerRemote.currentSong
binding.playerToolbar.apply { binding.run {
title = song.title title.text = song.title
subtitle = song.artistName text.text = song.artistName
} }
} }

View file

@ -35,7 +35,6 @@ import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentCirclePlayerBinding import code.name.monkey.retromusic.databinding.FragmentCirclePlayerBinding
import code.name.monkey.retromusic.extensions.* import code.name.monkey.retromusic.extensions.*
import code.name.monkey.retromusic.fragments.MusicSeekSkipTouchListener import code.name.monkey.retromusic.fragments.MusicSeekSkipTouchListener
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.base.goToAlbum import code.name.monkey.retromusic.fragments.base.goToAlbum
import code.name.monkey.retromusic.fragments.base.goToArtist import code.name.monkey.retromusic.fragments.base.goToArtist
@ -315,16 +314,10 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
val progressSlider = binding.progressSlider val progressSlider = binding.progressSlider
progressSlider.valueTo = total.toFloat() progressSlider.valueTo = total.toFloat()
if (isSeeking) { progressSlider.valueTo = total.toFloat()
progressSlider.value = progress.toFloat()
} else { progressSlider.value =
progressAnimator = progress.toFloat().coerceIn(progressSlider.valueFrom, progressSlider.valueTo)
ObjectAnimator.ofFloat(progressSlider, "value", progress.toFloat()).apply {
duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME
interpolator = LinearInterpolator()
start()
}
}
binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())

View file

@ -28,6 +28,7 @@ import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.extensions.whichFragment import code.name.monkey.retromusic.extensions.whichFragment
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.base.goToArtist import code.name.monkey.retromusic.fragments.base.goToArtist
import code.name.monkey.retromusic.fragments.player.CoverLyricsFragment
import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment
import code.name.monkey.retromusic.glide.GlideApp import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.RetroGlideExtension
@ -98,6 +99,7 @@ class FullPlayerFragment : AbsPlayerFragment(R.layout.fragment_full) {
controlsFragment.setColor(color) controlsFragment.setColor(color)
libraryViewModel.updateColor(color.backgroundColor) libraryViewModel.updateColor(color.backgroundColor)
ToolbarContentTintHelper.colorizeToolbar(binding.playerToolbar, Color.WHITE, activity) ToolbarContentTintHelper.colorizeToolbar(binding.playerToolbar, Color.WHITE, activity)
binding.coverLyrics.getFragment<CoverLyricsFragment>().setColors(color)
} }
override fun onFavoriteToggled() { override fun onFavoriteToggled() {

View file

@ -22,7 +22,6 @@ import android.graphics.PorterDuff
import android.graphics.drawable.AnimatedVectorDrawable import android.graphics.drawable.AnimatedVectorDrawable
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.animation.LinearInterpolator
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
@ -41,11 +40,11 @@ import code.name.monkey.retromusic.adapter.song.PlayingQueueAdapter
import code.name.monkey.retromusic.databinding.FragmentGradientPlayerBinding import code.name.monkey.retromusic.databinding.FragmentGradientPlayerBinding
import code.name.monkey.retromusic.extensions.* import code.name.monkey.retromusic.extensions.*
import code.name.monkey.retromusic.fragments.MusicSeekSkipTouchListener import code.name.monkey.retromusic.fragments.MusicSeekSkipTouchListener
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.base.goToAlbum import code.name.monkey.retromusic.fragments.base.goToAlbum
import code.name.monkey.retromusic.fragments.base.goToArtist import code.name.monkey.retromusic.fragments.base.goToArtist
import code.name.monkey.retromusic.fragments.other.VolumeFragment import code.name.monkey.retromusic.fragments.other.VolumeFragment
import code.name.monkey.retromusic.fragments.player.CoverLyricsFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
@ -272,6 +271,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
updateRepeatState() updateRepeatState()
updateShuffleState() updateShuffleState()
updatePrevNextColor() updatePrevNextColor()
binding.coverLyrics.getFragment<CoverLyricsFragment>().setColors(color)
} }
override fun onFavoriteToggled() { override fun onFavoriteToggled() {
@ -381,17 +381,22 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
private fun setUpPlayPauseFab() { private fun setUpPlayPauseFab() {
binding.playbackControlsFragment.playPauseButton.setOnClickListener( binding.playbackControlsFragment.playPauseButton.setOnClickListener(
PlayPauseButtonOnClickHandler()) PlayPauseButtonOnClickHandler()
)
} }
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
private fun setUpPrevNext() { private fun setUpPrevNext() {
updatePrevNextColor() updatePrevNextColor()
binding.playbackControlsFragment.nextButton.setOnTouchListener(MusicSeekSkipTouchListener( binding.playbackControlsFragment.nextButton.setOnTouchListener(
MusicSeekSkipTouchListener(
requireActivity(), requireActivity(),
true)) true
)
)
binding.playbackControlsFragment.previousButton.setOnTouchListener( binding.playbackControlsFragment.previousButton.setOnTouchListener(
MusicSeekSkipTouchListener(requireActivity(), false)) MusicSeekSkipTouchListener(requireActivity(), false)
)
} }
private fun updatePrevNextColor() { private fun updatePrevNextColor() {
@ -573,16 +578,9 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
val progressSlider = binding.playbackControlsFragment.progressSlider val progressSlider = binding.playbackControlsFragment.progressSlider
progressSlider.valueTo = total.toFloat() progressSlider.valueTo = total.toFloat()
if (isSeeking) { progressSlider.value =
progressSlider.value = progress.toFloat() progress.toFloat().coerceIn(progressSlider.valueFrom, progressSlider.valueTo)
} else {
progressAnimator =
ObjectAnimator.ofFloat(progressSlider, "value", progress.toFloat()).apply {
duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME
interpolator = LinearInterpolator()
start()
}
}
binding.playbackControlsFragment.songTotalTime.text = binding.playbackControlsFragment.songTotalTime.text =
MusicUtil.getReadableDurationString(total.toLong()) MusicUtil.getReadableDurationString(total.toLong())
binding.playbackControlsFragment.songCurrentProgress.text = binding.playbackControlsFragment.songCurrentProgress.text =

View file

@ -23,7 +23,9 @@ import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper
import code.name.monkey.retromusic.interfaces.ICabCallback import code.name.monkey.retromusic.interfaces.ICabCallback
import code.name.monkey.retromusic.interfaces.ICabHolder import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.RetroColorUtil import code.name.monkey.retromusic.util.RetroColorUtil
import code.name.monkey.retromusic.util.ThemedFastScroller
import com.afollestad.materialcab.attached.AttachedCab import com.afollestad.materialcab.attached.AttachedCab
import com.afollestad.materialcab.attached.destroy import com.afollestad.materialcab.attached.destroy
import com.afollestad.materialcab.attached.isActive import com.afollestad.materialcab.attached.isActive
@ -71,6 +73,8 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
binding.container.transitionName = "playlist" binding.container.transitionName = "playlist"
playlist = arguments.extraPlaylist playlist = arguments.extraPlaylist
binding.toolbar.title = playlist.playlistEntity.playlistName binding.toolbar.title = playlist.playlistEntity.playlistName
binding.toolbar.subtitle =
MusicUtil.getPlaylistInfoString(requireContext(), playlist.songs.toSongs())
setUpRecyclerView() setUpRecyclerView()
viewModel.getSongs().observe(viewLifecycleOwner) { viewModel.getSongs().observe(viewLifecycleOwner) {
songs(it.toSongs()) songs(it.toSongs())
@ -109,6 +113,7 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
binding.recyclerView.apply { binding.recyclerView.apply {
layoutManager = LinearLayoutManager(requireContext()) layoutManager = LinearLayoutManager(requireContext())
binding.recyclerView.adapter = wrappedAdapter binding.recyclerView.adapter = wrappedAdapter
ThemedFastScroller.create(this)
} }
playlistSongAdapter.registerAdapterDataObserver(object : playlistSongAdapter.registerAdapterDataObserver(object :
RecyclerView.AdapterDataObserver() { RecyclerView.AdapterDataObserver() {

View file

@ -24,13 +24,13 @@ import code.name.monkey.retromusic.EXTRA_PLAYLIST
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.playlist.PlaylistAdapter import code.name.monkey.retromusic.adapter.playlist.PlaylistAdapter
import code.name.monkey.retromusic.db.PlaylistWithSongs import code.name.monkey.retromusic.db.PlaylistWithSongs
import code.name.monkey.retromusic.extensions.setUpMediaRouteButton
import code.name.monkey.retromusic.fragments.ReloadType import code.name.monkey.retromusic.fragments.ReloadType
import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewCustomGridSizeFragment import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewCustomGridSizeFragment
import code.name.monkey.retromusic.helper.SortOrder.PlaylistSortOrder import code.name.monkey.retromusic.helper.SortOrder.PlaylistSortOrder
import code.name.monkey.retromusic.interfaces.IPlaylistClickListener import code.name.monkey.retromusic.interfaces.IPlaylistClickListener
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil
import com.google.android.gms.cast.framework.CastButtonFactory
import com.google.android.material.transition.MaterialSharedAxis import com.google.android.material.transition.MaterialSharedAxis
class PlaylistsFragment : class PlaylistsFragment :
@ -85,7 +85,7 @@ class PlaylistsFragment :
setUpSortOrderMenu(menu.findItem(R.id.action_sort_order).subMenu) setUpSortOrderMenu(menu.findItem(R.id.action_sort_order).subMenu)
MenuCompat.setGroupDividerEnabled(menu, true) MenuCompat.setGroupDividerEnabled(menu, true)
//Setting up cast button //Setting up cast button
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.action_cast) requireContext().setUpMediaRouteButton(menu)
} }
override fun onMenuItemSelected(item: MenuItem): Boolean { override fun onMenuItemSelected(item: MenuItem): Boolean {

View file

@ -26,6 +26,7 @@ import androidx.preference.PreferenceManager
import code.name.monkey.appthemehelper.common.prefs.supportv7.ATEPreferenceFragmentCompat import code.name.monkey.appthemehelper.common.prefs.supportv7.ATEPreferenceFragmentCompat
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.dip import code.name.monkey.retromusic.extensions.dip
import code.name.monkey.retromusic.extensions.goToProVersion
import code.name.monkey.retromusic.extensions.showToast import code.name.monkey.retromusic.extensions.showToast
import code.name.monkey.retromusic.preferences.* import code.name.monkey.retromusic.preferences.*
import code.name.monkey.retromusic.util.NavigationUtil import code.name.monkey.retromusic.util.NavigationUtil
@ -39,7 +40,7 @@ abstract class AbsSettingsFragment : ATEPreferenceFragmentCompat() {
internal fun showProToastAndNavigate(message: String) { internal fun showProToastAndNavigate(message: String) {
showToast(getString(R.string.message_pro_feature, message)) showToast(getString(R.string.message_pro_feature, message))
NavigationUtil.goToProVersion(requireActivity()) requireContext().goToProVersion()
} }
internal fun setSummary(preference: Preference, value: Any?) { internal fun setSummary(preference: Preference, value: Any?) {

View file

@ -27,6 +27,7 @@ import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentMainSettingsBinding import code.name.monkey.retromusic.databinding.FragmentMainSettingsBinding
import code.name.monkey.retromusic.extensions.drawAboveSystemBarsWithPadding import code.name.monkey.retromusic.extensions.drawAboveSystemBarsWithPadding
import code.name.monkey.retromusic.extensions.goToProVersion
import code.name.monkey.retromusic.util.NavigationUtil import code.name.monkey.retromusic.util.NavigationUtil
class MainSettingsFragment : Fragment(), View.OnClickListener { class MainSettingsFragment : Fragment(), View.OnClickListener {
@ -77,11 +78,11 @@ class MainSettingsFragment : Fragment(), View.OnClickListener {
binding.buyProContainer.apply { binding.buyProContainer.apply {
isGone = App.isProVersion() isGone = App.isProVersion()
setOnClickListener { setOnClickListener {
NavigationUtil.goToProVersion(requireContext()) requireContext().goToProVersion()
} }
} }
binding.buyPremium.setOnClickListener { binding.buyPremium.setOnClickListener {
NavigationUtil.goToProVersion(requireContext()) requireContext().goToProVersion()
} }
ThemeStore.accentColor(requireContext()).let { ThemeStore.accentColor(requireContext()).let {
binding.buyPremium.setTextColor(it) binding.buyPremium.setTextColor(it)

View file

@ -21,12 +21,10 @@ import code.name.monkey.appthemehelper.common.prefs.supportv7.ATEListPreference
import code.name.monkey.retromusic.LANGUAGE_NAME import code.name.monkey.retromusic.LANGUAGE_NAME
import code.name.monkey.retromusic.LAST_ADDED_CUTOFF import code.name.monkey.retromusic.LAST_ADDED_CUTOFF
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.installLanguageAndRecreate
import code.name.monkey.retromusic.fragments.LibraryViewModel import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.fragments.ReloadType.HomeSections import code.name.monkey.retromusic.fragments.ReloadType.HomeSections
import com.google.android.play.core.splitinstall.SplitInstallManagerFactory
import com.google.android.play.core.splitinstall.SplitInstallRequest
import org.koin.androidx.viewmodel.ext.android.sharedViewModel import org.koin.androidx.viewmodel.ext.android.sharedViewModel
import java.util.*
/** /**
* @author Hemanth S (h4h13). * @author Hemanth S (h4h13).
@ -58,21 +56,7 @@ class OtherSettingsFragment : AbsSettingsFragment() {
val languagePreference: Preference? = findPreference(LANGUAGE_NAME) val languagePreference: Preference? = findPreference(LANGUAGE_NAME)
languagePreference?.setOnPreferenceChangeListener { prefs, newValue -> languagePreference?.setOnPreferenceChangeListener { prefs, newValue ->
setSummary(prefs, newValue) setSummary(prefs, newValue)
val code = newValue.toString() requireActivity().installLanguageAndRecreate(newValue.toString())
val manager = SplitInstallManagerFactory.create(requireContext())
if (code != "auto") {
// Try to download language resources
val request =
SplitInstallRequest.newBuilder().addLanguage(Locale.forLanguageTag(code))
.build()
manager.startInstall(request)
// Recreate the activity on download complete
.addOnCompleteListener {
restartActivity()
}
} else {
requireActivity().recreate()
}
true true
} }
} }

View file

@ -21,6 +21,7 @@ import androidx.annotation.LayoutRes
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.song.SongAdapter import code.name.monkey.retromusic.adapter.song.SongAdapter
import code.name.monkey.retromusic.extensions.setUpMediaRouteButton
import code.name.monkey.retromusic.extensions.surfaceColor import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.fragments.GridStyle import code.name.monkey.retromusic.fragments.GridStyle
import code.name.monkey.retromusic.fragments.ReloadType import code.name.monkey.retromusic.fragments.ReloadType
@ -35,7 +36,6 @@ import com.afollestad.materialcab.attached.AttachedCab
import com.afollestad.materialcab.attached.destroy import com.afollestad.materialcab.attached.destroy
import com.afollestad.materialcab.attached.isActive import com.afollestad.materialcab.attached.isActive
import com.afollestad.materialcab.createCab import com.afollestad.materialcab.createCab
import com.google.android.gms.cast.framework.CastButtonFactory
class SongsFragment : AbsRecyclerViewCustomGridSizeFragment<SongAdapter, GridLayoutManager>(), class SongsFragment : AbsRecyclerViewCustomGridSizeFragment<SongAdapter, GridLayoutManager>(),
ICabHolder { ICabHolder {
@ -136,7 +136,7 @@ class SongsFragment : AbsRecyclerViewCustomGridSizeFragment<SongAdapter, GridLay
setupLayoutMenu(layoutItem.subMenu) setupLayoutMenu(layoutItem.subMenu)
setUpSortOrderMenu(menu.findItem(R.id.action_sort_order).subMenu) setUpSortOrderMenu(menu.findItem(R.id.action_sort_order).subMenu)
//Setting up cast button //Setting up cast button
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.action_cast) requireContext().setUpMediaRouteButton(menu)
} }
private fun setUpSortOrderMenu(sortOrderMenu: SubMenu) { private fun setUpSortOrderMenu(sortOrderMenu: SubMenu) {

View file

@ -27,10 +27,10 @@ import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.showToast import code.name.monkey.retromusic.extensions.showToast
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.repository.SongRepository import code.name.monkey.retromusic.repository.SongRepository
import code.name.monkey.retromusic.service.CastPlayer
import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.getExternalStorageDirectory import code.name.monkey.retromusic.util.getExternalStorageDirectory
import code.name.monkey.retromusic.util.logE import code.name.monkey.retromusic.util.logE
import com.google.android.gms.cast.framework.CastSession
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
import java.io.File import java.io.File
@ -171,9 +171,6 @@ object MusicPlayerRemote : KoinComponent {
return musicService?.playingQueue?.size ?: -1 return musicService?.playingQueue?.size ?: -1
} }
/**
* Async
*/
fun playSongAt(position: Int) { fun playSongAt(position: Int) {
musicService?.playSongAt(position) musicService?.playSongAt(position)
} }
@ -456,8 +453,8 @@ object MusicPlayerRemote : KoinComponent {
.dropLastWhile { it.isEmpty() }.toTypedArray()[1] .dropLastWhile { it.isEmpty() }.toTypedArray()[1]
} }
fun switchToRemotePlayback(castSession: CastSession) { fun switchToRemotePlayback(castPlayer: CastPlayer) {
musicService?.switchToRemotePlayback(castSession) musicService?.switchToRemotePlayback(castPlayer)
} }
fun switchToLocalPlayback() { fun switchToLocalPlayback() {

View file

@ -24,9 +24,10 @@ class MusicProgressViewUpdateHelper : Handler {
private var callback: Callback? = null private var callback: Callback? = null
private var intervalPlaying: Int = 0 private var intervalPlaying: Int = 0
private var intervalPaused: Int = 0 private var intervalPaused: Int = 0
private var firstUpdate = true
fun start() { fun start() {
queueNextRefresh(1) queueNextRefresh(refreshProgressViews().toLong())
} }
fun stop() { fun stop() {
@ -59,10 +60,11 @@ class MusicProgressViewUpdateHelper : Handler {
private fun refreshProgressViews(): Int { private fun refreshProgressViews(): Int {
val progressMillis = MusicPlayerRemote.songProgressMillis val progressMillis = MusicPlayerRemote.songProgressMillis
val totalMillis = MusicPlayerRemote.songDurationMillis val totalMillis = MusicPlayerRemote.songDurationMillis
if (totalMillis > 0) if (totalMillis > 0) {
firstUpdate = false
callback?.onUpdateProgressViews(progressMillis, totalMillis) callback?.onUpdateProgressViews(progressMillis, totalMillis)
}
if (!MusicPlayerRemote.isPlaying) { if (!MusicPlayerRemote.isPlaying && !firstUpdate) {
return intervalPaused return intervalPaused
} }
@ -84,7 +86,7 @@ class MusicProgressViewUpdateHelper : Handler {
companion object { companion object {
private const val CMD_REFRESH_PROGRESS_VIEWS = 1 private const val CMD_REFRESH_PROGRESS_VIEWS = 1
private const val MIN_INTERVAL = 20 private const val MIN_INTERVAL = 20
private const val UPDATE_INTERVAL_PLAYING = 1000 private const val UPDATE_INTERVAL_PLAYING = 500
private const val UPDATE_INTERVAL_PAUSED = 500 private const val UPDATE_INTERVAL_PAUSED = 500
} }
} }

View file

@ -19,7 +19,6 @@ import android.content.Context
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Paint import android.graphics.Paint
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.AsyncTask
import android.os.Looper import android.os.Looper
import android.text.Layout import android.text.Layout
import android.text.StaticLayout import android.text.StaticLayout
@ -35,14 +34,15 @@ import android.widget.Scroller
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.withSave import androidx.core.graphics.withSave
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import kotlinx.coroutines.*
import java.io.File import java.io.File
import java.lang.Runnable
import kotlin.math.abs import kotlin.math.abs
/** /**
* 歌词 Created by wcy on 2015/11/9. * 歌词 Created by wcy on 2015/11/9.
*/ */
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
@Suppress("deprecation")
class CoverLrcView @JvmOverloads constructor( class CoverLrcView @JvmOverloads constructor(
context: Context?, context: Context?,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
@ -72,7 +72,6 @@ class CoverLrcView @JvmOverloads constructor(
private var mScroller: Scroller? = null private var mScroller: Scroller? = null
private var mOffset = 0f private var mOffset = 0f
private var mCurrentLine = 0 private var mCurrentLine = 0
private var flag: Any? = null
private var isShowTimeline = false private var isShowTimeline = false
private var isTouching = false private var isTouching = false
private var isFling = false private var isFling = false
@ -85,9 +84,8 @@ class CoverLrcView @JvmOverloads constructor(
} }
} }
/** private val viewScope = CoroutineScope(Dispatchers.Main + Job())
* 手势监听器
*/
private val mSimpleOnGestureListener: SimpleOnGestureListener = private val mSimpleOnGestureListener: SimpleOnGestureListener =
object : SimpleOnGestureListener() { object : SimpleOnGestureListener() {
override fun onDown(e: MotionEvent): Boolean { override fun onDown(e: MotionEvent): Boolean {
@ -251,42 +249,31 @@ class CoverLrcView @JvmOverloads constructor(
mScroller = Scroller(context) mScroller = Scroller(context)
} }
/** 设置非当前行歌词字体颜色 */
fun setNormalColor(normalColor: Int) { fun setNormalColor(normalColor: Int) {
mNormalTextColor = normalColor mNormalTextColor = normalColor
postInvalidate() postInvalidate()
} }
/** 设置当前行歌词的字体颜色 */
fun setCurrentColor(currentColor: Int) { fun setCurrentColor(currentColor: Int) {
mCurrentTextColor = currentColor mCurrentTextColor = currentColor
postInvalidate() postInvalidate()
} }
/** 设置拖动歌词时选中歌词的字体颜色 */
fun setTimelineTextColor(timelineTextColor: Int) { fun setTimelineTextColor(timelineTextColor: Int) {
mTimelineTextColor = timelineTextColor mTimelineTextColor = timelineTextColor
postInvalidate() postInvalidate()
} }
/** 设置拖动歌词时时间线的颜色 */
fun setTimelineColor(timelineColor: Int) { fun setTimelineColor(timelineColor: Int) {
mTimelineColor = timelineColor mTimelineColor = timelineColor
postInvalidate() postInvalidate()
} }
/** 设置拖动歌词时右侧时间字体颜色 */
fun setTimeTextColor(timeTextColor: Int) { fun setTimeTextColor(timeTextColor: Int) {
mTimeTextColor = timeTextColor mTimeTextColor = timeTextColor
postInvalidate() postInvalidate()
} }
/**
* 设置歌词是否允许拖动
*
* @param draggable 是否允许拖动
* @param onPlayClickListener 设置歌词拖动后播放按钮点击监听器如果允许拖动则不能为 null
*/
fun setDraggable(draggable: Boolean, onPlayClickListener: OnPlayClickListener?) { fun setDraggable(draggable: Boolean, onPlayClickListener: OnPlayClickListener?) {
mOnPlayClickListener = if (draggable) { mOnPlayClickListener = if (draggable) {
requireNotNull(onPlayClickListener) { "if draggable == true, onPlayClickListener must not be null" } requireNotNull(onPlayClickListener) { "if draggable == true, onPlayClickListener must not be null" }
@ -296,17 +283,6 @@ class CoverLrcView @JvmOverloads constructor(
} }
} }
/**
* 设置播放按钮点击监听器
*
* @param onPlayClickListener 如果为非 null 则激活歌词拖动功能否则将将禁用歌词拖动功能
*/
@Deprecated("use {@link #setDraggable(boolean, OnPlayClickListener)} instead")
fun setOnPlayClickListener(onPlayClickListener: OnPlayClickListener?) {
mOnPlayClickListener = onPlayClickListener
}
/** 设置歌词为空时屏幕中央显示的文字,如“暂无歌词” */
fun setLabel(label: String?) { fun setLabel(label: String?) {
runOnUi { runOnUi {
mDefaultLabel = label mDefaultLabel = label
@ -314,106 +290,40 @@ class CoverLrcView @JvmOverloads constructor(
} }
} }
/**
* 加载歌词文件
*
* @param lrcFile 歌词文件
*/
fun loadLrc(lrcFile: File) { fun loadLrc(lrcFile: File) {
loadLrc(lrcFile, null)
}
/**
* 加载双语歌词文件两种语言的歌词时间戳需要一致
*
* @param mainLrcFile 第一种语言歌词文件
* @param secondLrcFile 第二种语言歌词文件
*/
private fun loadLrc(mainLrcFile: File, secondLrcFile: File?) {
runOnUi { runOnUi {
reset() reset()
val sb = StringBuilder("file://") viewScope.launch(Dispatchers.IO) {
sb.append(mainLrcFile.path) val lrcEntries = LrcUtils.parseLrc(arrayOf(lrcFile, null))
if (secondLrcFile != null) { withContext(Dispatchers.Main) {
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) onLrcLoaded(lrcEntries)
this@CoverLrcView.flag = null
} }
} }
}.execute(mainLrcFile, secondLrcFile)
} }
} }
/**
* 加载歌词文本
*
* @param lrcText 歌词文本
*/
fun loadLrc(lrcText: String?) { fun loadLrc(lrcText: String?) {
loadLrc(lrcText, null)
}
/**
* 加载双语歌词文本两种语言的歌词时间戳需要一致
*
* @param mainLrcText 第一种语言歌词文本
* @param secondLrcText 第二种语言歌词文本
*/
private fun loadLrc(mainLrcText: String?, secondLrcText: String?) {
runOnUi { runOnUi {
reset() reset()
val sb = StringBuilder("file://") viewScope.launch(Dispatchers.IO) {
sb.append(mainLrcText) val lrcEntries = LrcUtils.parseLrc(arrayOf(lrcText, null))
if (secondLrcText != null) { withContext(Dispatchers.Main) {
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) onLrcLoaded(lrcEntries)
this@CoverLrcView.flag = null
} }
} }
}.execute(mainLrcText, secondLrcText)
} }
} }
/**
* 歌词是否有效
*
* @return true如果歌词有效否则false
*/
fun hasLrc(): Boolean { fun hasLrc(): Boolean {
return mLrcEntryList.isNotEmpty() return mLrcEntryList.isNotEmpty()
} }
/**
* 刷新歌词
*
* @param time 当前播放时间
*/
fun updateTime(time: Long) { fun updateTime(time: Long) {
runOnUi { runOnUi {
if (!hasLrc()) { if (!hasLrc()) {
return@runOnUi return@runOnUi
} }
val line = findShowLine(time) val line = findShowLine(time + 300L)
if (line != mCurrentLine) { if (line != mCurrentLine) {
mCurrentLine = line mCurrentLine = line
if (!isShowTimeline) { if (!isShowTimeline) {
@ -441,9 +351,9 @@ class CoverLrcView @JvmOverloads constructor(
super.onDraw(canvas) super.onDraw(canvas)
val centerY = height / 2 val centerY = height / 2
// 无歌词文件
if (!hasLrc()) { if (!hasLrc()) {
mLrcPaint.color = mCurrentTextColor mLrcPaint.color = mCurrentTextColor
@Suppress("Deprecation")
@SuppressLint("DrawAllocation") val staticLayout = StaticLayout( @SuppressLint("DrawAllocation") val staticLayout = StaticLayout(
mDefaultLabel, mDefaultLabel,
mLrcPaint, mLrcPaint,
@ -485,11 +395,6 @@ class CoverLrcView @JvmOverloads constructor(
} }
} }
/**
* 画一行歌词
*
* @param y 歌词中心 Y 坐标
*/
private fun drawText(canvas: Canvas, staticLayout: StaticLayout, y: Float) { private fun drawText(canvas: Canvas, staticLayout: StaticLayout, y: Float) {
canvas.withSave { canvas.withSave {
translate(mLrcPadding, y - (staticLayout.height shr 1)) translate(mLrcPadding, y - (staticLayout.height shr 1))
@ -539,6 +444,7 @@ class CoverLrcView @JvmOverloads constructor(
override fun onDetachedFromWindow() { override fun onDetachedFromWindow() {
removeCallbacks(hideTimelineRunnable) removeCallbacks(hideTimelineRunnable)
viewScope.cancel()
super.onDetachedFromWindow() super.onDetachedFromWindow()
} }
@ -582,12 +488,10 @@ class CoverLrcView @JvmOverloads constructor(
invalidate() invalidate()
} }
/** 将中心行微调至正中心 */
private fun adjustCenter() { private fun adjustCenter() {
smoothScrollTo(centerLine, ADJUST_DURATION) smoothScrollTo(centerLine, ADJUST_DURATION)
} }
/** 滚动到某一行 */
private fun smoothScrollTo(line: Int, duration: Long = mAnimationDuration) { private fun smoothScrollTo(line: Int, duration: Long = mAnimationDuration) {
val offset = getOffset(line) val offset = getOffset(line)
endAnimation() endAnimation()
@ -602,14 +506,12 @@ class CoverLrcView @JvmOverloads constructor(
} }
} }
/** 结束滚动动画 */
private fun endAnimation() { private fun endAnimation() {
if (mAnimator != null && mAnimator!!.isRunning) { if (mAnimator != null && mAnimator!!.isRunning) {
mAnimator!!.end() mAnimator!!.end()
} }
} }
/** 二分法查找当前时间应该显示的行数(最后一个 <= time 的行数) */
private fun findShowLine(time: Long): Int { private fun findShowLine(time: Long): Int {
var left = 0 var left = 0
var right = mLrcEntryList.size var right = mLrcEntryList.size
@ -628,7 +530,6 @@ class CoverLrcView @JvmOverloads constructor(
return 0 return 0
} }
/** 获取当前在视图中央的行数 */
private val centerLine: Int private val centerLine: Int
get() { get() {
var centerLine = 0 var centerLine = 0
@ -642,7 +543,6 @@ class CoverLrcView @JvmOverloads constructor(
return centerLine return centerLine
} }
/** 获取歌词距离视图顶部的距离 采用懒加载方式 */
private fun getOffset(line: Int): Float { private fun getOffset(line: Int): Float {
if (mLrcEntryList.isEmpty()) return 0F if (mLrcEntryList.isEmpty()) return 0F
if (mLrcEntryList[line].offset == Float.MIN_VALUE) { if (mLrcEntryList[line].offset == Float.MIN_VALUE) {
@ -656,11 +556,9 @@ class CoverLrcView @JvmOverloads constructor(
return mLrcEntryList[line].offset return mLrcEntryList[line].offset
} }
/** 获取歌词宽度 */
private val lrcWidth: Float private val lrcWidth: Float
get() = width - mLrcPadding * 2 get() = width - mLrcPadding * 2
/** 在主线程中运行 */
private fun runOnUi(r: Runnable) { private fun runOnUi(r: Runnable) {
if (Looper.myLooper() == Looper.getMainLooper()) { if (Looper.myLooper() == Looper.getMainLooper()) {
r.run() r.run()
@ -669,13 +567,7 @@ class CoverLrcView @JvmOverloads constructor(
} }
} }
/** 播放按钮点击监听器,点击后应该跳转到指定播放位置 */
fun interface OnPlayClickListener { fun interface OnPlayClickListener {
/**
* 播放按钮被点击应该跳转到指定播放位置
*
* @return 是否成功消费该事件如果成功消费则会更新UI
*/
fun onPlayClick(time: Long): Boolean fun onPlayClick(time: Long): Boolean
} }

View file

@ -35,6 +35,8 @@ import android.view.View;
import android.view.animation.LinearInterpolator; import android.view.animation.LinearInterpolator;
import android.widget.Scroller; import android.widget.Scroller;
import androidx.core.content.ContextCompat;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -90,9 +92,7 @@ public class LrcView extends View {
} }
} }
}; };
/**
* 手势监听器
*/
private final GestureDetector.SimpleOnGestureListener mSimpleOnGestureListener = private final GestureDetector.SimpleOnGestureListener mSimpleOnGestureListener =
new GestureDetector.SimpleOnGestureListener() { new GestureDetector.SimpleOnGestureListener() {
@Override @Override
@ -111,7 +111,7 @@ public class LrcView extends View {
@Override @Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (hasLrc()) { if (hasLrc()) {
mOffset += -distanceY; mOffset -= distanceY;
mOffset = Math.min(mOffset, getOffset(0)); mOffset = Math.min(mOffset, getOffset(0));
mOffset = Math.max(mOffset, getOffset(mLrcEntryList.size() - 1)); mOffset = Math.max(mOffset, getOffset(mLrcEntryList.size() - 1));
invalidate(); invalidate();
@ -219,7 +219,7 @@ public class LrcView extends View {
mPlayDrawable = ta.getDrawable(R.styleable.LrcView_lrcPlayDrawable); mPlayDrawable = ta.getDrawable(R.styleable.LrcView_lrcPlayDrawable);
mPlayDrawable = mPlayDrawable =
(mPlayDrawable == null) (mPlayDrawable == null)
? getResources().getDrawable(R.drawable.ic_play_arrow) ? ContextCompat.getDrawable(getContext(), R.drawable.ic_play_arrow)
: mPlayDrawable; : mPlayDrawable;
mTimeTextColor = mTimeTextColor =
ta.getColor( ta.getColor(
@ -252,52 +252,28 @@ public class LrcView extends View {
mScroller = new Scroller(getContext()); mScroller = new Scroller(getContext());
} }
/** 设置非当前行歌词字体颜色 */
public void setNormalColor(int normalColor) {
mNormalTextColor = normalColor;
postInvalidate();
}
/** 普通歌词文本字体大小 */
public void setNormalTextSize(float size) {
mNormalTextSize = size;
}
/** 当前歌词文本字体大小 */
public void setCurrentTextSize(float size) {
mCurrentTextSize = size;
}
/** 设置当前行歌词的字体颜色 */
public void setCurrentColor(int currentColor) { public void setCurrentColor(int currentColor) {
mCurrentTextColor = currentColor; mCurrentTextColor = currentColor;
postInvalidate(); postInvalidate();
} }
/** 设置拖动歌词时选中歌词的字体颜色 */
public void setTimelineTextColor(int timelineTextColor) { public void setTimelineTextColor(int timelineTextColor) {
mTimelineTextColor = timelineTextColor; mTimelineTextColor = timelineTextColor;
postInvalidate(); postInvalidate();
} }
/** 设置拖动歌词时时间线的颜色 */
public void setTimelineColor(int timelineColor) { public void setTimelineColor(int timelineColor) {
mTimelineColor = timelineColor; mTimelineColor = timelineColor;
postInvalidate(); postInvalidate();
} }
/** 设置拖动歌词时右侧时间字体颜色 */
public void setTimeTextColor(int timeTextColor) { public void setTimeTextColor(int timeTextColor) {
mTimeTextColor = timeTextColor; mTimeTextColor = timeTextColor;
postInvalidate(); postInvalidate();
} }
/**
* 设置歌词是否允许拖动
*
* @param draggable 是否允许拖动
* @param onPlayClickListener 设置歌词拖动后播放按钮点击监听器如果允许拖动则不能为 null
*/
public void setDraggable(boolean draggable, OnPlayClickListener onPlayClickListener) { public void setDraggable(boolean draggable, OnPlayClickListener onPlayClickListener) {
if (draggable) { if (draggable) {
if (onPlayClickListener == null) { if (onPlayClickListener == null) {
@ -310,18 +286,6 @@ public class LrcView extends View {
} }
} }
/**
* 设置播放按钮点击监听器
*
* @param onPlayClickListener 如果为非 null 则激活歌词拖动功能否则将将禁用歌词拖动功能
* @deprecated use {@link #setDraggable(boolean, OnPlayClickListener)} instead
*/
@Deprecated
public void setOnPlayClickListener(OnPlayClickListener onPlayClickListener) {
mOnPlayClickListener = onPlayClickListener;
}
/** 设置歌词为空时屏幕中央显示的文字,如“暂无歌词” */
public void setLabel(String label) { public void setLabel(String label) {
runOnUi( runOnUi(
() -> { () -> {
@ -330,24 +294,12 @@ public class LrcView extends View {
}); });
} }
/**
* 加载歌词文件
*
* @param lrcFile 歌词文件
*/
public void loadLrc(File lrcFile) { public void loadLrc(File lrcFile) {
loadLrc(lrcFile, null); loadLrc(lrcFile, null);
} }
/**
* 加载双语歌词文件两种语言的歌词时间戳需要一致
*
* @param mainLrcFile 第一种语言歌词文件
* @param secondLrcFile 第二种语言歌词文件
*/
public void loadLrc(File mainLrcFile, File secondLrcFile) { public void loadLrc(File mainLrcFile, File secondLrcFile) {
runOnUi( runOnUi(() -> {
() -> {
reset(); reset();
StringBuilder sb = new StringBuilder("file://"); StringBuilder sb = new StringBuilder("file://");
@ -374,21 +326,10 @@ public class LrcView extends View {
}); });
} }
/**
* 加载歌词文本
*
* @param lrcText 歌词文本
*/
public void loadLrc(String lrcText) { public void loadLrc(String lrcText) {
loadLrc(lrcText, null); loadLrc(lrcText, null);
} }
/**
* 加载双语歌词文本两种语言的歌词时间戳需要一致
*
* @param mainLrcText 第一种语言歌词文本
* @param secondLrcText 第二种语言歌词文本
*/
public void loadLrc(String mainLrcText, String secondLrcText) { public void loadLrc(String mainLrcText, String secondLrcText) {
runOnUi( runOnUi(
() -> { () -> {
@ -418,53 +359,10 @@ public class LrcView extends View {
}); });
} }
/**
* 加载在线歌词默认使用 utf-8 编码
*
* @param lrcUrl 歌词文件的网络地址
*/
public void loadLrcByUrl(String lrcUrl) {
loadLrcByUrl(lrcUrl, "utf-8");
}
/**
* 加载在线歌词
*
* @param lrcUrl 歌词文件的网络地址
* @param charset 编码格式
*/
public void loadLrcByUrl(String lrcUrl, String charset) {
String flag = "url://" + lrcUrl;
setFlag(flag);
new AsyncTask<String, Integer, String>() {
@Override
protected String doInBackground(String... params) {
return LrcUtils.getContentFromNetwork(params[0], params[1]);
}
@Override
protected void onPostExecute(String lrcText) {
if (getFlag() == flag) {
loadLrc(lrcText);
}
}
}.execute(lrcUrl, charset);
}
/**
* 歌词是否有效
*
* @return true如果歌词有效否则false
*/
public boolean hasLrc() { public boolean hasLrc() {
return !mLrcEntryList.isEmpty(); return !mLrcEntryList.isEmpty();
} }
/**
* 刷新歌词
*
* @param time 当前播放时间
*/
public void updateTime(long time) { public void updateTime(long time) {
runOnUi( runOnUi(
() -> { () -> {
@ -484,12 +382,6 @@ public class LrcView extends View {
}); });
} }
/**
* 将歌词滚动到指定时间
*
* @param time 指定的时间
* @deprecated 请使用 {@link #updateTime(long)} 代替
*/
@Deprecated @Deprecated
public void onDrag(long time) { public void onDrag(long time) {
updateTime(time); updateTime(time);
@ -513,7 +405,6 @@ public class LrcView extends View {
int centerY = getHeight() / 2; int centerY = getHeight() / 2;
// 无歌词文件
if (!hasLrc()) { if (!hasLrc()) {
mLrcPaint.setColor(mCurrentTextColor); mLrcPaint.setColor(mCurrentTextColor);
@SuppressLint("DrawAllocation") @SuppressLint("DrawAllocation")
@ -570,11 +461,6 @@ public class LrcView extends View {
} }
} }
/**
* 画一行歌词
*
* @param y 歌词中心 Y 坐标
*/
private void drawText(Canvas canvas, StaticLayout staticLayout, float y) { private void drawText(Canvas canvas, StaticLayout staticLayout, float y) {
canvas.save(); canvas.save();
canvas.translate(mLrcPadding, y - (staticLayout.getHeight() >> 1)); canvas.translate(mLrcPadding, y - (staticLayout.getHeight() >> 1));
@ -661,17 +547,14 @@ public class LrcView extends View {
invalidate(); invalidate();
} }
/** 将中心行微调至正中心 */
private void adjustCenter() { private void adjustCenter() {
smoothScrollTo(getCenterLine(), ADJUST_DURATION); smoothScrollTo(getCenterLine(), ADJUST_DURATION);
} }
/** 滚动到某一行 */
private void smoothScrollTo(int line) { private void smoothScrollTo(int line) {
smoothScrollTo(line, mAnimationDuration); smoothScrollTo(line, mAnimationDuration);
} }
/** 滚动到某一行 */
private void smoothScrollTo(int line, long duration) { private void smoothScrollTo(int line, long duration) {
float offset = getOffset(line); float offset = getOffset(line);
endAnimation(); endAnimation();
@ -687,14 +570,12 @@ public class LrcView extends View {
mAnimator.start(); mAnimator.start();
} }
/** 结束滚动动画 */
private void endAnimation() { private void endAnimation() {
if (mAnimator != null && mAnimator.isRunning()) { if (mAnimator != null && mAnimator.isRunning()) {
mAnimator.end(); mAnimator.end();
} }
} }
/** 二分法查找当前时间应该显示的行数(最后一个 <= time 的行数) */
private int findShowLine(long time) { private int findShowLine(long time) {
int left = 0; int left = 0;
int right = mLrcEntryList.size(); int right = mLrcEntryList.size();
@ -716,7 +597,6 @@ public class LrcView extends View {
return 0; return 0;
} }
/** 获取当前在视图中央的行数 */
private int getCenterLine() { private int getCenterLine() {
int centerLine = 0; int centerLine = 0;
float minDistance = Float.MAX_VALUE; float minDistance = Float.MAX_VALUE;
@ -729,7 +609,6 @@ public class LrcView extends View {
return centerLine; return centerLine;
} }
/** 获取歌词距离视图顶部的距离 采用懒加载方式 */
private float getOffset(int line) { private float getOffset(int line) {
if (mLrcEntryList.get(line).getOffset() == Float.MIN_VALUE) { if (mLrcEntryList.get(line).getOffset() == Float.MIN_VALUE) {
float offset = getHeight() / 2; float offset = getHeight() / 2;
@ -744,12 +623,10 @@ public class LrcView extends View {
return mLrcEntryList.get(line).getOffset(); return mLrcEntryList.get(line).getOffset();
} }
/** 获取歌词宽度 */
private float getLrcWidth() { private float getLrcWidth() {
return getWidth() - mLrcPadding * 2; return getWidth() - mLrcPadding * 2;
} }
/** 在主线程中运行 */
private void runOnUi(Runnable r) { private void runOnUi(Runnable r) {
if (Looper.myLooper() == Looper.getMainLooper()) { if (Looper.myLooper() == Looper.getMainLooper()) {
r.run(); r.run();
@ -766,13 +643,7 @@ public class LrcView extends View {
this.mFlag = flag; this.mFlag = flag;
} }
/** 播放按钮点击监听器,点击后应该跳转到指定播放位置 */
public interface OnPlayClickListener { public interface OnPlayClickListener {
/**
* 播放按钮被点击应该跳转到指定播放位置
*
* @return 是否成功消费该事件如果成功消费则会更新UI
*/
boolean onPlayClick(long time); boolean onPlayClick(long time);
} }
} }

View file

@ -81,7 +81,7 @@ class AlbumCoverStylePreferenceDialog : DialogFragment(),
if (isAlbumCoverStyle(coverStyle)) { if (isAlbumCoverStyle(coverStyle)) {
val result = getString(coverStyle.titleRes) + " theme is Pro version feature." val result = getString(coverStyle.titleRes) + " theme is Pro version feature."
showToast(result) showToast(result)
NavigationUtil.goToProVersion(requireActivity()) requireContext().goToProVersion()
} else { } else {
PreferenceUtil.albumCoverStyle = coverStyle PreferenceUtil.albumCoverStyle = coverStyle
} }

View file

@ -91,7 +91,7 @@ class NowPlayingScreenPreferenceDialog : DialogFragment(), ViewPager.OnPageChang
val result = val result =
"${getString(nowPlayingScreen.titleRes)} theme is Pro version feature." "${getString(nowPlayingScreen.titleRes)} theme is Pro version feature."
showToast(result) showToast(result)
NavigationUtil.goToProVersion(requireContext()) requireContext().goToProVersion()
} else { } else {
PreferenceUtil.nowPlayingScreen = nowPlayingScreen PreferenceUtil.nowPlayingScreen = nowPlayingScreen
} }

View file

@ -3,8 +3,6 @@ package code.name.monkey.retromusic.service
import android.animation.Animator import android.animation.Animator
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.media.MediaPlayer import android.media.MediaPlayer
import android.os.Handler
import android.os.Looper
import androidx.core.animation.doOnEnd import androidx.core.animation.doOnEnd
import code.name.monkey.retromusic.service.playback.Playback import code.name.monkey.retromusic.service.playback.Playback
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
@ -12,16 +10,16 @@ import code.name.monkey.retromusic.util.PreferenceUtil
class AudioFader { class AudioFader {
companion object { companion object {
inline fun createFadeAnimator( fun createFadeAnimator(
fadeInMp: MediaPlayer, fadeInMp: MediaPlayer,
fadeOutMp: MediaPlayer, fadeOutMp: MediaPlayer,
crossinline endAction: (animator: Animator) -> Unit, /* Code to run when Animator Ends*/ endAction: (animator: Animator) -> Unit, /* Code to run when Animator Ends*/
): Animator? { ): Animator? {
val duration = PreferenceUtil.crossFadeDuration * 1000 val duration = PreferenceUtil.crossFadeDuration * 1000
if (duration == 0) { if (duration == 0) {
return null return null
} }
return ValueAnimator.ofFloat(1f, 0f).apply { return ValueAnimator.ofFloat(0f, 1f).apply {
this.duration = duration.toLong() this.duration = duration.toLong()
addUpdateListener { animation: ValueAnimator -> addUpdateListener { animation: ValueAnimator ->
fadeInMp.setVolume( fadeInMp.setVolume(
@ -36,15 +34,14 @@ class AudioFader {
} }
} }
@JvmStatic
fun startFadeAnimator( fun startFadeAnimator(
playback: Playback, playback: Playback,
fadeIn: Boolean, /* fadeIn -> true fadeOut -> false*/ fadeIn: Boolean, /* fadeIn -> true fadeOut -> false*/
callback: Runnable, /* Code to run when Animator Ends*/ callback: Runnable? = null, /* Code to run when Animator Ends*/
) { ) {
val duration = PreferenceUtil.audioFadeDuration.toLong() val duration = PreferenceUtil.audioFadeDuration.toLong()
if (duration == 0L) { if (duration == 0L) {
callback.run() callback?.run()
return return
} }
val startValue = if (fadeIn) 0f else 1.0f val startValue = if (fadeIn) 0f else 1.0f
@ -52,16 +49,12 @@ class AudioFader {
val animator = ValueAnimator.ofFloat(startValue, endValue) val animator = ValueAnimator.ofFloat(startValue, endValue)
animator.duration = duration animator.duration = duration
animator.addUpdateListener { animation: ValueAnimator -> animator.addUpdateListener { animation: ValueAnimator ->
playback.setVolume( playback.setVolume(animation.animatedValue as Float)
animation.animatedValue as Float
)
} }
animator.doOnEnd { animator.doOnEnd {
callback.run() callback?.run()
} }
Handler(Looper.getMainLooper()).post {
animator.start() animator.start()
} }
} }
} }
}

View file

@ -37,7 +37,7 @@ class CrossFadePlayer(context: Context) : LocalPlayback(context) {
private var crossFadeAnimator: Animator? = null private var crossFadeAnimator: Animator? = null
override var callbacks: PlaybackCallbacks? = null override var callbacks: PlaybackCallbacks? = null
private var crossFadeDuration = PreferenceUtil.crossFadeDuration private var crossFadeDuration = PreferenceUtil.crossFadeDuration
private var isCrossFading = false var isCrossFading = false
init { init {
player1.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK) player1.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK)
@ -120,17 +120,25 @@ class CrossFadePlayer(context: Context) : LocalPlayback(context) {
override val isPlaying: Boolean override val isPlaying: Boolean
get() = mIsInitialized && getCurrentPlayer()?.isPlaying == true get() = mIsInitialized && getCurrentPlayer()?.isPlaying == true
override fun setDataSource(song: Song, force: Boolean): Boolean { override fun setDataSource(
song: Song,
force: Boolean,
completion: (success: Boolean) -> Unit,
) {
if (force) hasDataSource = false if (force) hasDataSource = false
mIsInitialized = false mIsInitialized = false
/* We've already set DataSource if initialized is true in setNextDataSource */ /* We've already set DataSource if initialized is true in setNextDataSource */
return if (!hasDataSource) { if (!hasDataSource) {
mIsInitialized = setDataSourceImpl(getCurrentPlayer()!!, song.uri.toString()) getCurrentPlayer()?.let {
setDataSourceImpl(it, song.uri.toString()) { success ->
mIsInitialized = success
completion(success)
}
}
hasDataSource = true hasDataSource = true
mIsInitialized
} else { } else {
completion(true)
mIsInitialized = true mIsInitialized = true
true
} }
} }
@ -285,8 +293,8 @@ class CrossFadePlayer(context: Context) : LocalPlayback(context) {
val nextSong = MusicPlayerRemote.nextSong val nextSong = MusicPlayerRemote.nextSong
// Switch to other player (Crossfade) only if next song exists // Switch to other player (Crossfade) only if next song exists
if (nextSong != null) { if (nextSong != null) {
if (setDataSourceImpl(player, nextSong.uri.toString())) { setDataSourceImpl(player, nextSong.uri.toString()) { success ->
switchPlayer() if (success) switchPlayer()
} }
} }
} }
@ -295,7 +303,7 @@ class CrossFadePlayer(context: Context) : LocalPlayback(context) {
private fun switchPlayer() { private fun switchPlayer() {
getNextPlayer()?.start() getNextPlayer()?.start()
crossFade(getCurrentPlayer()!!, getNextPlayer()!!) crossFade(getNextPlayer()!!, getCurrentPlayer()!!)
currentPlayer = currentPlayer =
if (currentPlayer == CurrentPlayer.PLAYER_ONE || currentPlayer == CurrentPlayer.NOT_SET) { if (currentPlayer == CurrentPlayer.PLAYER_ONE || currentPlayer == CurrentPlayer.NOT_SET) {
CurrentPlayer.PLAYER_TWO CurrentPlayer.PLAYER_TWO
@ -325,6 +333,10 @@ internal fun crossFadeScope(): CoroutineScope = CoroutineScope(Job() + Dispatche
fun MediaPlayer.setPlaybackSpeedPitch(speed: Float, pitch: Float) { fun MediaPlayer.setPlaybackSpeedPitch(speed: Float, pitch: Float) {
if (hasMarshmallow()) { if (hasMarshmallow()) {
val wasPlaying = isPlaying
playbackParams = PlaybackParams().setSpeed(speed).setPitch(pitch) playbackParams = PlaybackParams().setSpeed(speed).setPitch(pitch)
if (!wasPlaying) {
pause()
}
} }
} }

View file

@ -109,10 +109,13 @@ abstract class LocalPlayback(val context: Context) : Playback, MediaPlayer.OnErr
* @param path The path of the file, or the http/rtsp URL of the stream you want to play * @param path The path of the file, or the http/rtsp URL of the stream you want to play
* @return True if the <code>player</code> has been prepared and is ready to play, false otherwise * @return True if the <code>player</code> has been prepared and is ready to play, false otherwise
*/ */
fun setDataSourceImpl(player: MediaPlayer, path: String): Boolean { fun setDataSourceImpl(
try { player: MediaPlayer,
path: String,
completion: (success: Boolean) -> Unit,
) {
player.reset() player.reset()
player.setOnPreparedListener(null) try {
if (path.startsWith("content://")) { if (path.startsWith("content://")) {
player.setDataSource(context, path.toUri()) player.setDataSource(context, path.toUri())
} else { } else {
@ -123,14 +126,17 @@ abstract class LocalPlayback(val context: Context) : Playback, MediaPlayer.OnErr
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build() .build()
) )
player.prepare() player.setOnPreparedListener {
player.setOnPreparedListener(null)
completion(true)
}
player.prepareAsync()
} catch (e: Exception) { } catch (e: Exception) {
completion(false)
e.printStackTrace() e.printStackTrace()
return false
} }
player.setOnCompletionListener(this) player.setOnCompletionListener(this)
player.setOnErrorListener(this) player.setOnErrorListener(this)
return true
} }
private fun unregisterBecomingNoisyReceiver() { private fun unregisterBecomingNoisyReceiver() {

View file

@ -0,0 +1,200 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.service
import android.content.Context
import android.content.Intent
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.os.PowerManager
import android.os.PowerManager.WakeLock
import android.util.Log
import android.view.KeyEvent
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.media.session.MediaButtonReceiver
import code.name.monkey.retromusic.BuildConfig
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_PAUSE
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_PLAY
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_REWIND
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_STOP
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
/**
* Used to control headset playback.
* Single press: pause/resume
* Double press: actionNext track
* Triple press: previous track
*/
class MediaButtonIntentReceiver : MediaButtonReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (DEBUG) Log.v(TAG, "Received intent: $intent")
if (handleIntent(context, intent) && isOrderedBroadcast) {
abortBroadcast()
}
}
companion object {
val TAG: String = MediaButtonIntentReceiver::class.java.simpleName
private val DEBUG = BuildConfig.DEBUG
private const val MSG_HEADSET_DOUBLE_CLICK_TIMEOUT = 2
private const val DOUBLE_CLICK = 400
private var wakeLock: WakeLock? = null
private var mClickCounter = 0
private var mLastClickTime: Long = 0
private val mHandler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
when (msg.what) {
MSG_HEADSET_DOUBLE_CLICK_TIMEOUT -> {
val clickCount = msg.arg1
if (DEBUG) Log.v(TAG, "Handling headset click, count = $clickCount")
val command = when (clickCount) {
1 -> ACTION_TOGGLE_PAUSE
2 -> ACTION_SKIP
3 -> ACTION_REWIND
else -> null
}
if (command != null) {
val context = msg.obj as Context
startService(context, command)
}
}
}
releaseWakeLockIfHandlerIdle()
}
}
fun handleIntent(context: Context, intent: Intent): Boolean {
println("Intent Action: ${intent.action}")
val intentAction = intent.action
if (Intent.ACTION_MEDIA_BUTTON == intentAction) {
val event = intent.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT)
?: return false
val keycode = event.keyCode
val action = event.action
val eventTime = if (event.eventTime != 0L)
event.eventTime
else
System.currentTimeMillis()
var command: String? = null
when (keycode) {
KeyEvent.KEYCODE_MEDIA_STOP -> command = ACTION_STOP
KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> command =
ACTION_TOGGLE_PAUSE
KeyEvent.KEYCODE_MEDIA_NEXT -> command = ACTION_SKIP
KeyEvent.KEYCODE_MEDIA_PREVIOUS -> command = ACTION_REWIND
KeyEvent.KEYCODE_MEDIA_PAUSE -> command = ACTION_PAUSE
KeyEvent.KEYCODE_MEDIA_PLAY -> command = ACTION_PLAY
}
if (command != null) {
if (action == KeyEvent.ACTION_DOWN) {
if (event.repeatCount == 0) {
// Only consider the first event in a sequence, not the repeat events,
// so that we don't trigger in cases where the first event went to
// a different app (e.g. when the user ends a phone call by
// long pressing the headset button)
// The service may or may not be running, but we need to send it
// a command.
if (keycode == KeyEvent.KEYCODE_HEADSETHOOK || keycode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
if (eventTime - mLastClickTime >= DOUBLE_CLICK) {
mClickCounter = 0
}
mClickCounter++
if (DEBUG) Log.v(TAG, "Got headset click, count = $mClickCounter")
mHandler.removeMessages(MSG_HEADSET_DOUBLE_CLICK_TIMEOUT)
val msg = mHandler.obtainMessage(
MSG_HEADSET_DOUBLE_CLICK_TIMEOUT, mClickCounter, 0, context
)
val delay = (if (mClickCounter < 3) DOUBLE_CLICK else 0).toLong()
if (mClickCounter >= 3) {
mClickCounter = 0
}
mLastClickTime = eventTime
acquireWakeLockAndSendMessage(context, msg, delay)
} else {
startService(context, command)
}
return true
}
}
}
}
return false
}
private fun startService(context: Context, command: String?) {
val intent = Intent(context, MusicService::class.java)
intent.action = command
try {
// IMPORTANT NOTE: (kind of a hack)
// on Android O and above the following crashes when the app is not running
// there is no good way to check whether the app is running so we catch the exception
// we do not always want to use startForegroundService() because then one gets an ANR
// if no notification is displayed via startForeground()
// according to Play analytics this happens a lot, I suppose for example if command = PAUSE
context.startService(intent)
} catch (ignored: IllegalStateException) {
ContextCompat.startForegroundService(context, intent)
}
}
private fun acquireWakeLockAndSendMessage(context: Context, msg: Message, delay: Long) {
if (wakeLock == null) {
val appContext = context.applicationContext
val pm = appContext.getSystemService<PowerManager>()
wakeLock = pm?.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK,
"RetroMusicApp:Wakelock headset button"
)
wakeLock!!.setReferenceCounted(false)
}
if (DEBUG) Log.v(TAG, "Acquiring wake lock and sending " + msg.what)
// Make sure we don't indefinitely hold the wake lock under any circumstances
wakeLock!!.acquire(10000)
mHandler.sendMessageDelayed(msg, delay)
}
private fun releaseWakeLockIfHandlerIdle() {
if (mHandler.hasMessages(MSG_HEADSET_DOUBLE_CLICK_TIMEOUT)) {
if (DEBUG) Log.v(TAG, "Handler still has messages pending, not releasing wake lock")
return
}
if (wakeLock != null) {
if (DEBUG) Log.v(TAG, "Releasing wake lock")
wakeLock!!.release()
wakeLock = null
}
}
}
}

View file

@ -46,13 +46,19 @@ class MultiPlayer(context: Context) : LocalPlayback(context) {
* @param song The song object you want to play * @param song The song object you want to play
* @return True if the `player` has been prepared and is ready to play, false otherwise * @return True if the `player` has been prepared and is ready to play, false otherwise
*/ */
override fun setDataSource(song: Song, force: Boolean): Boolean { override fun setDataSource(
song: Song,
force: Boolean,
completion: (success: Boolean) -> Unit,
) {
isInitialized = false isInitialized = false
isInitialized = setDataSourceImpl(mCurrentMediaPlayer, song.uri.toString()) setDataSourceImpl(mCurrentMediaPlayer, song.uri.toString()) { success ->
isInitialized = success
if (isInitialized) { if (isInitialized) {
setNextDataSource(null) setNextDataSource(null)
} }
return isInitialized completion(isInitialized)
}
} }
/** /**
@ -80,7 +86,8 @@ class MultiPlayer(context: Context) : LocalPlayback(context) {
mNextMediaPlayer = MediaPlayer() mNextMediaPlayer = MediaPlayer()
mNextMediaPlayer?.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK) mNextMediaPlayer?.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK)
mNextMediaPlayer?.audioSessionId = audioSessionId mNextMediaPlayer?.audioSessionId = audioSessionId
if (setDataSourceImpl(mNextMediaPlayer!!, path)) { setDataSourceImpl(mNextMediaPlayer!!, path) { success ->
if (success) {
try { try {
mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer) mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
@ -104,6 +111,7 @@ class MultiPlayer(context: Context) : LocalPlayback(context) {
} }
} }
} }
}
/** /**
* Starts or resumes playback. * Starts or resumes playback.

View file

@ -15,6 +15,7 @@ package code.name.monkey.retromusic.service
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent
import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetManager
import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothDevice.EXTRA_DEVICE import android.bluetooth.BluetoothDevice.EXTRA_DEVICE
@ -39,7 +40,6 @@ import android.widget.Toast
import androidx.core.content.edit import androidx.core.content.edit
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.media.MediaBrowserServiceCompat import androidx.media.MediaBrowserServiceCompat
import androidx.media.session.MediaButtonReceiver.handleIntent
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import code.name.monkey.appthemehelper.util.VersionUtils import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.* import code.name.monkey.retromusic.*
@ -86,7 +86,6 @@ import code.name.monkey.retromusic.volume.OnAudioVolumeChangedListener
import com.bumptech.glide.request.target.CustomTarget import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.target.Target
import com.bumptech.glide.request.transition.Transition import com.bumptech.glide.request.transition.Transition
import com.google.android.gms.cast.framework.CastSession
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.Default import kotlinx.coroutines.Dispatchers.Default
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
@ -94,6 +93,7 @@ import kotlinx.coroutines.Dispatchers.Main
import org.koin.java.KoinJavaComponent.get import org.koin.java.KoinJavaComponent.get
import java.util.* import java.util.*
/** /**
* @author Karim Abou Zeid (kabouzeid), Andrew Neal. Modified by Prathamesh More * @author Karim Abou Zeid (kabouzeid), Andrew Neal. Modified by Prathamesh More
*/ */
@ -117,11 +117,6 @@ class MusicService : MediaBrowserServiceCompat(),
private var trackEndedByCrossfade = false private var trackEndedByCrossfade = false
private val serviceScope = CoroutineScope(Job() + Main) private val serviceScope = CoroutineScope(Job() + Main)
// Every chromecast method needs to run on main thread or you are greeted with IllegalStateException
// So it will use Main dispatcher
// And by using Default dispatcher for local playback we are reducing the burden from main thread
private val playerDispatcher get() = if (playbackManager.isLocalPlayback) Default else Main
@JvmField @JvmField
var position = -1 var position = -1
private val appWidgetBig = AppWidgetBig.instance private val appWidgetBig = AppWidgetBig.instance
@ -208,8 +203,7 @@ class MusicService : MediaBrowserServiceCompat(),
} }
} }
} }
private var queueSaveHandler: QueueSaveHandler? = null
private var queueSaveHandlerThread: HandlerThread? = null
private var queuesRestored = false private var queuesRestored = false
var repeatMode = 0 var repeatMode = 0
@ -282,12 +276,6 @@ class MusicService : MediaBrowserServiceCompat(),
playbackManager.setCallbacks(this) playbackManager.setCallbacks(this)
setupMediaSession() setupMediaSession()
// queue saving needs to run on a separate thread so that it doesn't block the playback handler
// events
queueSaveHandlerThread =
HandlerThread("QueueSaveHandler", Process.THREAD_PRIORITY_BACKGROUND)
queueSaveHandlerThread?.start()
queueSaveHandler = QueueSaveHandler(this, queueSaveHandlerThread!!.looper)
uiThreadHandler = Handler(Looper.getMainLooper()) uiThreadHandler = Handler(Looper.getMainLooper())
registerReceiver(widgetIntentReceiver, IntentFilter(APP_WIDGET_UPDATE)) registerReceiver(widgetIntentReceiver, IntentFilter(APP_WIDGET_UPDATE))
registerReceiver(updateFavoriteReceiver, IntentFilter(FAVORITE_STATE_CHANGED)) registerReceiver(updateFavoriteReceiver, IntentFilter(FAVORITE_STATE_CHANGED))
@ -296,13 +284,17 @@ class MusicService : MediaBrowserServiceCompat(),
notificationManager = getSystemService() notificationManager = getSystemService()
initNotification() initNotification()
mediaStoreObserver = MediaStoreObserver(this, playerHandler!!) mediaStoreObserver = MediaStoreObserver(this, playerHandler!!)
throttledSeekHandler = ThrottledSeekHandler(this, playerHandler!!) throttledSeekHandler = ThrottledSeekHandler(this, Handler(mainLooper))
contentResolver.registerContentObserver(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, contentResolver.registerContentObserver(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
true, true,
mediaStoreObserver) mediaStoreObserver
contentResolver.registerContentObserver(MediaStore.Audio.Media.INTERNAL_CONTENT_URI, )
contentResolver.registerContentObserver(
MediaStore.Audio.Media.INTERNAL_CONTENT_URI,
true, true,
mediaStoreObserver) mediaStoreObserver
)
val audioVolumeObserver = AudioVolumeObserver(this) val audioVolumeObserver = AudioVolumeObserver(this)
audioVolumeObserver.register(AudioManager.STREAM_MUSIC, this) audioVolumeObserver.register(AudioManager.STREAM_MUSIC, this)
registerOnSharedPreferenceChangedListener(this) registerOnSharedPreferenceChangedListener(this)
@ -330,6 +322,7 @@ class MusicService : MediaBrowserServiceCompat(),
mediaSession?.isActive = false mediaSession?.isActive = false
quit() quit()
releaseResources() releaseResources()
serviceScope.cancel()
contentResolver.unregisterContentObserver(mediaStoreObserver) contentResolver.unregisterContentObserver(mediaStoreObserver)
unregisterOnSharedPreferenceChangedListener(this) unregisterOnSharedPreferenceChangedListener(this)
wakeLock?.release() wakeLock?.release()
@ -441,9 +434,12 @@ class MusicService : MediaBrowserServiceCompat(),
} }
private fun setPosition(position: Int) { private fun setPosition(position: Int) {
openTrackAndPrepareNextAt(position) openTrackAndPrepareNextAt(position) { success ->
if (success) {
notifyChange(PLAY_STATE_CHANGED) notifyChange(PLAY_STATE_CHANGED)
} }
}
}
private fun getPreviousPosition(force: Boolean): Int { private fun getPreviousPosition(force: Boolean): Int {
var newPosition = getPosition() - 1 var newPosition = getPosition() - 1
@ -634,6 +630,8 @@ class MusicService : MediaBrowserServiceCompat(),
if (playbackManager.maybeSwitchToCrossFade(crossFadeDuration)) { if (playbackManager.maybeSwitchToCrossFade(crossFadeDuration)) {
restorePlaybackState(wasPlaying, progress) restorePlaybackState(wasPlaying, progress)
} else {
playbackManager.setCrossFadeDuration(crossFadeDuration)
} }
} }
ALBUM_ART_ON_LOCK_SCREEN, BLURRED_ALBUM_ART -> updateMediaSessionMetaData(::updateMediaSessionPlaybackState) ALBUM_ART_ON_LOCK_SCREEN, BLURRED_ALBUM_ART -> updateMediaSessionMetaData(::updateMediaSessionPlaybackState)
@ -656,7 +654,6 @@ class MusicService : MediaBrowserServiceCompat(),
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent != null && intent.action != null) { if (intent != null && intent.action != null) {
handleIntent(mediaSession, intent)
serviceScope.launch { serviceScope.launch {
restoreQueuesAndPositionIfNecessary() restoreQueuesAndPositionIfNecessary()
when (intent.action) { when (intent.action) {
@ -757,15 +754,16 @@ class MusicService : MediaBrowserServiceCompat(),
} }
@Synchronized @Synchronized
fun openTrackAndPrepareNextAt(position: Int): Boolean { fun openTrackAndPrepareNextAt(position: Int, completion: (success: Boolean) -> Unit) {
this.position = position this.position = position
val prepared = openCurrent() openCurrent { success ->
if (prepared) { completion(success)
if (success) {
prepareNextImpl() prepareNextImpl()
} }
notifyChange(META_CHANGED) notifyChange(META_CHANGED)
notHandledMetaChangedForCurrentTrack = false notHandledMetaChangedForCurrentTrack = false
return prepared }
} }
fun pause(force: Boolean = false) { fun pause(force: Boolean = false) {
@ -776,12 +774,11 @@ class MusicService : MediaBrowserServiceCompat(),
@Synchronized @Synchronized
fun play() { fun play() {
playbackManager.play(onNotInitialized = { playSongAt(getPosition()) }) { playbackManager.play { playSongAt(getPosition()) }
if (notHandledMetaChangedForCurrentTrack) { if (notHandledMetaChangedForCurrentTrack) {
handleChangeInternal(META_CHANGED) handleChangeInternal(META_CHANGED)
notHandledMetaChangedForCurrentTrack = false notHandledMetaChangedForCurrentTrack = false
} }
}
notifyChange(PLAY_STATE_CHANGED) notifyChange(PLAY_STATE_CHANGED)
} }
@ -794,14 +791,21 @@ class MusicService : MediaBrowserServiceCompat(),
} }
fun playSongAt(position: Int) { fun playSongAt(position: Int) {
serviceScope.launch(playerDispatcher) { // Every chromecast method needs to run on main thread or you are greeted with IllegalStateException
if (openTrackAndPrepareNextAt(position)) { // So it will use Main dispatcher
// And by using Default dispatcher for local playback we are reduce the burden of main thread
serviceScope.launch(if (playbackManager.isLocalPlayback) Default else Main) {
openTrackAndPrepareNextAt(position) { success ->
if (success) {
play() play()
} else { } else {
runOnUiThread {
showToast(R.string.unplayable_file) showToast(R.string.unplayable_file)
} }
} }
} }
}
}
@Synchronized @Synchronized
fun prepareNextImpl() { fun prepareNextImpl() {
@ -913,14 +917,15 @@ class MusicService : MediaBrowserServiceCompat(),
originalPlayingQueue = ArrayList(restoredOriginalQueue) originalPlayingQueue = ArrayList(restoredOriginalQueue)
playingQueue = ArrayList(restoredQueue) playingQueue = ArrayList(restoredQueue)
position = restoredPosition position = restoredPosition
withContext(playerDispatcher) { withContext(Main) {
openCurrent() openCurrent {
prepareNext() prepareNext()
if (restoredPositionInTrack > 0) { if (restoredPositionInTrack > 0) {
seek(restoredPositionInTrack) seek(restoredPositionInTrack)
} }
notHandledMetaChangedForCurrentTrack = true notHandledMetaChangedForCurrentTrack = true
sendChangeInternal(META_CHANGED) sendChangeInternal(META_CHANGED)
}
if (receivedHeadsetConnected) { if (receivedHeadsetConnected) {
play() play()
receivedHeadsetConnected = false receivedHeadsetConnected = false
@ -946,17 +951,6 @@ class MusicService : MediaBrowserServiceCompat(),
} }
} }
fun saveQueuesImpl() {
MusicPlaybackQueueStore.getInstance(this).saveQueues(playingQueue, originalPlayingQueue)
}
fun saveState() {
saveQueues()
savePosition()
savePositionInTrack()
storage.saveSong(currentSong)
}
@Synchronized @Synchronized
fun seek(millis: Int): Int { fun seek(millis: Int): Int {
return try { return try {
@ -1024,8 +1018,10 @@ class MusicService : MediaBrowserServiceCompat(),
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.albumName) .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.albumName)
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.title) .putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.title)
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.duration) .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.duration)
.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, .putLong(
(getPosition() + 1).toLong()) MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER,
(getPosition() + 1).toLong()
)
.putLong(MediaMetadataCompat.METADATA_KEY_YEAR, song.year.toLong()) .putLong(MediaMetadataCompat.METADATA_KEY_YEAR, song.year.toLong())
.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, null) .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, null)
.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, playingQueue.size.toLong()) .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, playingQueue.size.toLong())
@ -1097,9 +1093,9 @@ class MusicService : MediaBrowserServiceCompat(),
// We must call updateMediaSessionPlaybackState after the load of album art is completed // We must call updateMediaSessionPlaybackState after the load of album art is completed
// if we are loading it or it won't be updated in the notification // if we are loading it or it won't be updated in the notification
updateMediaSessionMetaData(::updateMediaSessionPlaybackState) updateMediaSessionMetaData(::updateMediaSessionPlaybackState)
serviceScope.launch(IO) {
savePosition() savePosition()
savePositionInTrack() savePositionInTrack()
serviceScope.launch(IO) {
val currentSong = currentSong val currentSong = currentSong
HistoryStore.getInstance(this@MusicService).addSongId(currentSong.id) HistoryStore.getInstance(this@MusicService).addSongId(currentSong.id)
if (songPlayCountHelper.shouldBumpPlayCount()) { if (songPlayCountHelper.shouldBumpPlayCount()) {
@ -1107,13 +1103,14 @@ class MusicService : MediaBrowserServiceCompat(),
.bumpPlayCount(songPlayCountHelper.song.id) .bumpPlayCount(songPlayCountHelper.song.id)
} }
songPlayCountHelper.notifySongChanged(currentSong) songPlayCountHelper.notifySongChanged(currentSong)
storage.saveSong(currentSong)
} }
} }
QUEUE_CHANGED -> { QUEUE_CHANGED -> {
mediaSession?.setQueueTitle(getString(R.string.now_playing_queue)) mediaSession?.setQueueTitle(getString(R.string.now_playing_queue))
mediaSession?.setQueue(playingQueue.toMediaSessionQueue()) mediaSession?.setQueue(playingQueue.toMediaSessionQueue())
updateMediaSessionMetaData(::updateMediaSessionPlaybackState) // because playing queue size might have changed updateMediaSessionMetaData(::updateMediaSessionPlaybackState) // because playing queue size might have changed
saveState() saveQueues()
if (playingQueue.size > 0) { if (playingQueue.size > 0) {
prepareNext() prepareNext()
} else { } else {
@ -1164,18 +1161,15 @@ class MusicService : MediaBrowserServiceCompat(),
} }
@Synchronized @Synchronized
private fun openCurrent(): Boolean { private fun openCurrent(completion: (success: Boolean) -> Unit) {
val force = if (!trackEndedByCrossfade) { val force = if (!trackEndedByCrossfade) {
true true
} else { } else {
trackEndedByCrossfade = false trackEndedByCrossfade = false
false false
} }
return try { playbackManager.setDataSource(currentSong, force) { success ->
playbackManager.setDataSource(currentSong, force) completion(success)
} catch (e: Exception) {
e.printStackTrace()
false
} }
} }
@ -1183,16 +1177,20 @@ class MusicService : MediaBrowserServiceCompat(),
playbackManager.switchToLocalPlayback(this::restorePlaybackState) playbackManager.switchToLocalPlayback(this::restorePlaybackState)
} }
fun switchToRemotePlayback(castSession: CastSession) { fun switchToRemotePlayback(castPlayer: CastPlayer) {
playbackManager.switchToRemotePlayback(castSession, this::restorePlaybackState) playbackManager.switchToRemotePlayback(castPlayer, this::restorePlaybackState)
} }
private fun restorePlaybackState(wasPlaying: Boolean, progress: Int) { private fun restorePlaybackState(wasPlaying: Boolean, progress: Int) {
playbackManager.setCallbacks(this) playbackManager.setCallbacks(this)
if (openTrackAndPrepareNextAt(position)) { openTrackAndPrepareNextAt(position) { success ->
if (success) {
seek(progress) seek(progress)
if (wasPlaying) { if (wasPlaying) {
play() play()
} else {
pause()
}
} }
} }
playbackManager.setCrossFadeDuration(crossFadeDuration) playbackManager.setCrossFadeDuration(crossFadeDuration)
@ -1212,12 +1210,16 @@ class MusicService : MediaBrowserServiceCompat(),
openQueue(playlistSongs, 0, true) openQueue(playlistSongs, 0, true)
} }
} else { } else {
runOnUiThread {
showToast(R.string.playlist_is_empty, Toast.LENGTH_LONG) showToast(R.string.playlist_is_empty, Toast.LENGTH_LONG)
} }
}
} else { } else {
runOnUiThread {
showToast(R.string.playlist_is_empty, Toast.LENGTH_LONG) showToast(R.string.playlist_is_empty, Toast.LENGTH_LONG)
} }
} }
}
private fun prepareNext() { private fun prepareNext() {
prepareNextImpl() prepareNextImpl()
@ -1241,8 +1243,6 @@ class MusicService : MediaBrowserServiceCompat(),
private fun releaseResources() { private fun releaseResources() {
playerHandler?.removeCallbacksAndMessages(null) playerHandler?.removeCallbacksAndMessages(null)
musicPlayerHandlerThread?.quitSafely() musicPlayerHandlerThread?.quitSafely()
queueSaveHandler?.removeCallbacksAndMessages(null)
queueSaveHandlerThread?.quitSafely()
playbackManager.release() playbackManager.release()
mediaSession?.release() mediaSession?.release()
} }
@ -1269,8 +1269,10 @@ class MusicService : MediaBrowserServiceCompat(),
} }
private fun saveQueues() { private fun saveQueues() {
queueSaveHandler?.removeMessages(SAVE_QUEUES) serviceScope.launch(IO) {
queueSaveHandler?.sendEmptyMessage(SAVE_QUEUES) MusicPlaybackQueueStore.getInstance(this@MusicService)
.saveQueues(playingQueue, originalPlayingQueue)
}
} }
private fun sendChangeInternal(what: String) { private fun sendChangeInternal(what: String) {
@ -1308,13 +1310,27 @@ class MusicService : MediaBrowserServiceCompat(),
} }
private fun setupMediaSession() { private fun setupMediaSession() {
val mediaButtonReceiverComponentName = ComponentName(
applicationContext,
MediaButtonIntentReceiver::class.java
)
val mediaButtonIntent = Intent(Intent.ACTION_MEDIA_BUTTON)
mediaButtonIntent.component = mediaButtonReceiverComponentName
val mediaButtonReceiverPendingIntent = PendingIntent.getBroadcast(
applicationContext, 0, mediaButtonIntent,
if (VersionUtils.hasMarshmallow()) PendingIntent.FLAG_IMMUTABLE else 0
)
mediaSession = MediaSessionCompat( mediaSession = MediaSessionCompat(
this, this,
"RetroMusicPlayer" BuildConfig.APPLICATION_ID,
mediaButtonReceiverComponentName,
mediaButtonReceiverPendingIntent
) )
val mediaSessionCallback = MediaSessionCallback(this) val mediaSessionCallback = MediaSessionCallback(this)
mediaSession?.setCallback(mediaSessionCallback) mediaSession?.setCallback(mediaSessionCallback)
mediaSession?.isActive = true mediaSession?.isActive = true
mediaSession?.setMediaButtonReceiver(mediaButtonReceiverPendingIntent)
} }
inner class MusicBinder : Binder() { inner class MusicBinder : Binder() {
@ -1362,7 +1378,6 @@ class MusicService : MediaBrowserServiceCompat(),
const val REPEAT_MODE_NONE = 0 const val REPEAT_MODE_NONE = 0
const val REPEAT_MODE_ALL = 1 const val REPEAT_MODE_ALL = 1
const val REPEAT_MODE_THIS = 2 const val REPEAT_MODE_THIS = 2
const val SAVE_QUEUES = 0
private const val MEDIA_SESSION_ACTIONS = (PlaybackStateCompat.ACTION_PLAY private const val MEDIA_SESSION_ACTIONS = (PlaybackStateCompat.ACTION_PLAY
or PlaybackStateCompat.ACTION_PAUSE or PlaybackStateCompat.ACTION_PAUSE
or PlaybackStateCompat.ACTION_PLAY_PAUSE or PlaybackStateCompat.ACTION_PLAY_PAUSE

View file

@ -8,7 +8,6 @@ import code.name.monkey.retromusic.service.playback.Playback
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.PreferenceUtil.playbackPitch import code.name.monkey.retromusic.util.PreferenceUtil.playbackPitch
import code.name.monkey.retromusic.util.PreferenceUtil.playbackSpeed import code.name.monkey.retromusic.util.PreferenceUtil.playbackSpeed
import com.google.android.gms.cast.framework.CastSession
class PlaybackManager(val context: Context) { class PlaybackManager(val context: Context) {
@ -47,16 +46,19 @@ class PlaybackManager(val context: Context) {
playback?.callbacks = callbacks playback?.callbacks = callbacks
} }
fun play(onNotInitialized: () -> Unit = {}, onPlay: () -> Unit = {}) { fun play(onNotInitialized: () -> Unit) {
if (playback != null && !playback!!.isPlaying) { if (playback != null && !playback!!.isPlaying) {
if (!playback!!.isInitialized) { if (!playback!!.isInitialized) {
onNotInitialized() onNotInitialized()
} else { } else {
openAudioEffectSession() openAudioEffectSession()
if (playbackLocation == PlaybackLocation.LOCAL) { if (playbackLocation == PlaybackLocation.LOCAL) {
AudioFader.startFadeAnimator(playback!!, true) { if (playback is CrossFadePlayer) {
// Code when Animator Ends if (!(playback as CrossFadePlayer).isCrossFading) {
onPlay() AudioFader.startFadeAnimator(playback!!, true)
}
} else {
AudioFader.startFadeAnimator(playback!!, true)
} }
} }
if (shouldSetSpeed) { if (shouldSetSpeed) {
@ -86,8 +88,12 @@ class PlaybackManager(val context: Context) {
fun seek(millis: Int): Int = playback!!.seek(millis) fun seek(millis: Int): Int = playback!!.seek(millis)
fun setDataSource(song: Song, force: Boolean): Boolean { fun setDataSource(
return playback?.setDataSource(song, force) == true song: Song,
force: Boolean,
completion: (success: Boolean) -> Unit,
) {
playback?.setDataSource(song, force, completion)
} }
fun setNextDataSource(trackUri: String) { fun setNextDataSource(trackUri: String) {
@ -153,25 +159,22 @@ class PlaybackManager(val context: Context) {
} }
fun switchToRemotePlayback( fun switchToRemotePlayback(
castSession: CastSession, castPlayer: CastPlayer,
onChange: (wasPlaying: Boolean, progress: Int) -> Unit, onChange: (wasPlaying: Boolean, progress: Int) -> Unit,
) { ) {
playbackLocation = PlaybackLocation.REMOTE playbackLocation = PlaybackLocation.REMOTE
switchToPlayback(CastPlayer(castSession), onChange) switchToPlayback(castPlayer, onChange)
} }
private fun switchToPlayback( private fun switchToPlayback(
playback: Playback, playback: Playback,
onChange: (wasPlaying: Boolean, progress: Int) -> Unit, onChange: (wasPlaying: Boolean, progress: Int) -> Unit,
) { ) {
val oldPlayback = playback val oldPlayback = this.playback
val wasPlaying: Boolean = oldPlayback.isPlaying val wasPlaying: Boolean = oldPlayback?.isPlaying == true
val progress: Int = oldPlayback.position() val progress: Int = oldPlayback?.position() ?: 0
this.playback = playback this.playback = playback
oldPlayback?.stop()
oldPlayback.stop()
onChange(wasPlaying, progress) onChange(wasPlaying, progress)
} }

View file

@ -1,35 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.service
import android.os.Handler
import android.os.Looper
import android.os.Message
import code.name.monkey.retromusic.service.MusicService.Companion.SAVE_QUEUES
import java.lang.ref.WeakReference
internal class QueueSaveHandler(
musicService: MusicService,
looper: Looper
) : Handler(looper) {
private val service: WeakReference<MusicService> = WeakReference(musicService)
override fun handleMessage(msg: Message) {
val service: MusicService? = service.get()
if (msg.what == SAVE_QUEUES) {
service?.saveQueuesImpl()
}
}
}

View file

@ -25,7 +25,9 @@ interface Playback {
val audioSessionId: Int val audioSessionId: Int
fun setDataSource(song: Song, force: Boolean):Boolean fun setDataSource(
song: Song, force: Boolean, completion: (success: Boolean) -> Unit,
)
fun setNextDataSource(path: String?) fun setNextDataSource(path: String?)

View file

@ -22,7 +22,6 @@ import androidx.viewpager.widget.ViewPager
*/ */
class ParallaxPagerTransformer(private val id: Int) : ViewPager.PageTransformer { class ParallaxPagerTransformer(private val id: Int) : ViewPager.PageTransformer {
private var border = 0
private var speed = 0.2f private var speed = 0.2f
override fun transformPage(page: View, position: Float) { override fun transformPage(page: View, position: Float) {
@ -32,22 +31,12 @@ class ParallaxPagerTransformer(private val id: Int) : ViewPager.PageTransformer
if (position > -1 && position < 1) { if (position > -1 && position < 1) {
val width = parallaxView.width.toFloat() val width = parallaxView.width.toFloat()
parallaxView.translationX = -(position * width * speed) parallaxView.translationX = -(position * width * speed)
val sc = (width - border) / width
if (position == 0f) {
scaleX = 1f scaleX = 1f
scaleY = 1f scaleY = 1f
} else {
scaleX = sc
scaleY = sc
} }
} }
} }
} }
}
fun setBorder(px: Int) {
border = px
}
fun setSpeed(speed: Float) { fun setSpeed(speed: Float) {
this.speed = speed this.speed = speed

View file

@ -117,7 +117,7 @@ object LyricUtil {
return "$lrcRootPath$title - $artist.lrc" return "$lrcRootPath$title - $artist.lrc"
} }
fun getLrcOriginalPath(filePath: String): String { private fun getLrcOriginalPath(filePath: String): String {
return filePath.replace(filePath.substring(filePath.lastIndexOf(".") + 1), "lrc") return filePath.replace(filePath.substring(filePath.lastIndexOf(".") + 1), "lrc")
} }

View file

@ -39,12 +39,6 @@ object NavigationUtil {
) )
} }
fun goToProVersion(context: Context) {
context.startActivity(
Intent(context, PurchaseActivity::class.java), null
)
}
fun goToSupportDevelopment(activity: Activity) { fun goToSupportDevelopment(activity: Activity) {
activity.startActivity( activity.startActivity(
Intent(activity, SupportDevelopmentActivity::class.java), null Intent(activity, SupportDevelopmentActivity::class.java), null

View file

@ -115,7 +115,7 @@ object PreferenceUtil {
putString(SAF_SDCARD_URI, value) putString(SAF_SDCARD_URI, value)
} }
val autoDownloadImagesPolicy private val autoDownloadImagesPolicy
get() = sharedPreferences.getStringOrDefault( get() = sharedPreferences.getStringOrDefault(
AUTO_DOWNLOAD_IMAGES_POLICY, AUTO_DOWNLOAD_IMAGES_POLICY,
"only_wifi" "only_wifi"
@ -242,8 +242,6 @@ object PreferenceUtil {
val isScreenOnEnabled get() = sharedPreferences.getBoolean(KEEP_SCREEN_ON, false) val isScreenOnEnabled get() = sharedPreferences.getBoolean(KEEP_SCREEN_ON, false)
val isShuffleModeOn get() = sharedPreferences.getBoolean(TOGGLE_SHUFFLE, false)
val isSongInfo get() = sharedPreferences.getBoolean(EXTRA_SONG_INFO, false) val isSongInfo get() = sharedPreferences.getBoolean(EXTRA_SONG_INFO, false)
val isPauseOnZeroVolume get() = sharedPreferences.getBoolean(PAUSE_ON_ZERO_VOLUME, false) val isPauseOnZeroVolume get() = sharedPreferences.getBoolean(PAUSE_ON_ZERO_VOLUME, false)
@ -472,10 +470,10 @@ object PreferenceUtil {
val tabTitleMode: Int val tabTitleMode: Int
get() { get() {
return when (sharedPreferences.getStringOrDefault( return when (sharedPreferences.getStringOrDefault(
TAB_TEXT_MODE, "1" TAB_TEXT_MODE, "0"
).toInt()) { ).toInt()) {
1 -> BottomNavigationView.LABEL_VISIBILITY_LABELED
0 -> BottomNavigationView.LABEL_VISIBILITY_AUTO 0 -> BottomNavigationView.LABEL_VISIBILITY_AUTO
1 -> BottomNavigationView.LABEL_VISIBILITY_LABELED
2 -> BottomNavigationView.LABEL_VISIBILITY_SELECTED 2 -> BottomNavigationView.LABEL_VISIBILITY_SELECTED
3 -> BottomNavigationView.LABEL_VISIBILITY_UNLABELED 3 -> BottomNavigationView.LABEL_VISIBILITY_UNLABELED
else -> BottomNavigationView.LABEL_VISIBILITY_LABELED else -> BottomNavigationView.LABEL_VISIBILITY_LABELED
@ -697,11 +695,11 @@ object PreferenceUtil {
val isSnowFalling val isSnowFalling
get() = sharedPreferences.getBoolean(SNOWFALL, false) get() = sharedPreferences.getBoolean(SNOWFALL, false)
val lyricsType: LyricsType val lyricsType: CoverLyricsType
get() = if (sharedPreferences.getString(LYRICS_TYPE, "0") == "0") { get() = if (sharedPreferences.getString(LYRICS_TYPE, "0") == "0") {
LyricsType.REPLACE_COVER CoverLyricsType.REPLACE_COVER
} else { } else {
LyricsType.OVER_COVER CoverLyricsType.OVER_COVER
} }
var playbackSpeed var playbackSpeed
@ -740,6 +738,6 @@ object PreferenceUtil {
get() = sharedPreferences.getBoolean(SWIPE_DOWN_DISMISS, true) get() = sharedPreferences.getBoolean(SWIPE_DOWN_DISMISS, true)
} }
enum class LyricsType { enum class CoverLyricsType {
REPLACE_COVER, OVER_COVER REPLACE_COVER, OVER_COVER
} }

View file

@ -1,33 +0,0 @@
package code.name.monkey.retromusic.util
import android.content.Context
import android.content.Intent
import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.activities.PurchaseActivity
object PremiumShow {
private const val PREF_NAME = "premium_show"
private const val LAUNCH_COUNT = "launch_count"
private const val DATE_FIRST_LAUNCH = "date_first_launch"
@JvmStatic
fun launch(context: Context) {
val pref = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
if (App.isProVersion()) {
return
}
val prefEditor = pref.edit()
val launchCount = pref.getLong(LAUNCH_COUNT, 0) + 1
prefEditor.putLong(LAUNCH_COUNT, launchCount)
var dateLaunched = pref.getLong(DATE_FIRST_LAUNCH, 0)
if (dateLaunched == 0L) {
dateLaunched = System.currentTimeMillis()
prefEditor.putLong(DATE_FIRST_LAUNCH, dateLaunched)
}
if (System.currentTimeMillis() >= dateLaunched + 2 * 24 * 60 * 60 * 1000) {
context.startActivity(Intent(context, PurchaseActivity::class.java), null)
}
prefEditor.apply()
}
}

View file

@ -19,14 +19,13 @@ import android.content.res.ColorStateList
import android.util.AttributeSet import android.util.AttributeSet
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.NavigationViewUtil
import code.name.monkey.retromusic.extensions.addAlpha import code.name.monkey.retromusic.extensions.addAlpha
import code.name.monkey.retromusic.extensions.setItemColors
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView
import dev.chrisbanes.insetter.applyInsetter import dev.chrisbanes.insetter.applyInsetter
class BottomNavigationBarTinted @JvmOverloads constructor( class TintedBottomNavigationView @JvmOverloads constructor(
context: Context, context: Context,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
defStyleAttr: Int = 0, defStyleAttr: Int = 0,
@ -55,16 +54,7 @@ class BottomNavigationBarTinted @JvmOverloads constructor(
if (!PreferenceUtil.materialYou) { if (!PreferenceUtil.materialYou) {
val iconColor = ATHUtil.resolveColor(context, android.R.attr.colorControlNormal) val iconColor = ATHUtil.resolveColor(context, android.R.attr.colorControlNormal)
val accentColor = ThemeStore.accentColor(context) val accentColor = ThemeStore.accentColor(context)
NavigationViewUtil.setItemIconColors( setItemColors(iconColor, accentColor)
this,
ColorUtil.withAlpha(iconColor, 0.5f),
accentColor
)
NavigationViewUtil.setItemTextColors(
this,
ColorUtil.withAlpha(iconColor, 0.5f),
accentColor
)
itemRippleColor = ColorStateList.valueOf(accentColor.addAlpha(0.08F)) itemRippleColor = ColorStateList.valueOf(accentColor.addAlpha(0.08F))
itemActiveIndicatorColor = ColorStateList.valueOf(accentColor.addAlpha(0.12F)) itemActiveIndicatorColor = ColorStateList.valueOf(accentColor.addAlpha(0.12F))
} }

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.views
import android.content.Context
import android.content.res.ColorStateList
import android.util.AttributeSet
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.addAlpha
import code.name.monkey.retromusic.extensions.setItemColors
import code.name.monkey.retromusic.util.PreferenceUtil
import com.google.android.material.navigationrail.NavigationRailView
class TintedNavigationRailView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : NavigationRailView(context, attrs, defStyleAttr) {
init {
if (!isInEditMode) {
labelVisibilityMode = PreferenceUtil.tabTitleMode
if (!PreferenceUtil.materialYou) {
val iconColor = ATHUtil.resolveColor(context, android.R.attr.colorControlNormal)
val accentColor = context.accentColor()
setItemColors(iconColor, accentColor)
itemRippleColor = ColorStateList.valueOf(accentColor.addAlpha(0.08F))
itemActiveIndicatorColor = ColorStateList.valueOf(accentColor.addAlpha(0.12F))
}
}
}
}

View file

@ -5,6 +5,5 @@
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:fillType="evenOdd" android:pathData="M2,22V4Q2,3.175 2.588,2.587Q3.175,2 4,2H15Q15.825,2 16.413,2.587Q17,3.175 17,4V4.425Q15.625,5.025 14.812,6.262Q14,7.5 14,9Q14,10.5 14.812,11.738Q15.625,12.975 17,13.575V16Q17,16.825 16.413,17.413Q15.825,18 15,18H6ZM6,14H10V12H6ZM19,12Q17.75,12 16.875,11.125Q16,10.25 16,9Q16,7.725 16.875,6.862Q17.75,6 19,6Q19.275,6 19.525,6.05Q19.775,6.1 20,6.175V1H24V3H22V9Q22,10.25 21.125,11.125Q20.25,12 19,12ZM6,11H13V9H6ZM6,8H13V6H6Z"/>
android:pathData="M5,3C3.8954,3 3,3.8954 3,5V19C3,20.1046 3.8954,21 5,21H19C20.1046,21 21,20.1046 21,19V5C21,3.8954 20.1046,3 19,3H5ZM6,7C6,6.4477 6.4477,6 7,6H17C17.5523,6 18,6.4477 18,7C18,7.5523 17.5523,8 17,8H7C6.4477,8 6,7.5523 6,7ZM6,12C6,11.4477 6.4477,11 7,11H17C17.5523,11 18,11.4477 18,12C18,12.5523 17.5523,13 17,13H7C6.4477,13 6,12.5523 6,12ZM7,16C6.4477,16 6,16.4477 6,17C6,17.5523 6.4477,18 7,18H11C11.5523,18 12,17.5523 12,17C12,16.4477 11.5523,16 11,16H7Z" />
</vector> </vector>

View file

@ -4,17 +4,6 @@
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:pathData="M5,3L19,3A2,2 0,0 1,21 5L21,19A2,2 0,0 1,19 21L5,21A2,2 0,0 1,3 19L3,5A2,2 0,0 1,5 3z" android:fillColor="#fff"
android:strokeWidth="2" android:pathData="M2,22V4Q2,3.175 2.588,2.587Q3.175,2 4,2H15Q15.825,2 16.413,2.587Q17,3.175 17,4V4.425Q16.4,4.7 15.9,5.1Q15.4,5.5 15,6V4Q15,4 15,4Q15,4 15,4H4Q4,4 4,4Q4,4 4,4V16.175L4.175,16H15Q15,16 15,16Q15,16 15,16V12Q15.4,12.5 15.9,12.9Q16.4,13.3 17,13.575V16Q17,16.825 16.413,17.413Q15.825,18 15,18H6ZM6,14H10V12H6ZM19,12Q17.75,12 16.875,11.125Q16,10.25 16,9Q16,7.75 16.875,6.875Q17.75,6 19,6Q19.275,6 19.525,6.05Q19.775,6.1 20,6.175V1H24V3H22V9Q22,10.25 21.125,11.125Q20.25,12 19,12ZM6,11H13V9H6ZM6,8H13V6H6ZM4,16.175V4Q4,4 4,4Q4,4 4,4Q4,4 4,4Q4,4 4,4V6Q4,6.625 4,7.387Q4,8.15 4,9Q4,9.85 4,10.613Q4,11.375 4,12V16Q4,16 4,16Q4,16 4,16Z" />
android:fillColor="#00000000"
android:strokeColor="#fff"/>
<path
android:pathData="M7,6L17,6A1,1 0,0 1,18 7L18,7A1,1 0,0 1,17 8L7,8A1,1 0,0 1,6 7L6,7A1,1 0,0 1,7 6z"
android:fillColor="#fff"/>
<path
android:pathData="M7,11L17,11A1,1 0,0 1,18 12L18,12A1,1 0,0 1,17 13L7,13A1,1 0,0 1,6 12L6,12A1,1 0,0 1,7 11z"
android:fillColor="#fff"/>
<path
android:pathData="M7,16L11,16A1,1 0,0 1,12 17L12,17A1,1 0,0 1,11 18L7,18A1,1 0,0 1,6 17L6,17A1,1 0,0 1,7 16z"
android:fillColor="#fff"/>
</vector> </vector>

View file

@ -0,0 +1,74 @@
<?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"
android:background="?colorSurface"
android:clickable="true"
android:focusable="true">
<include layout="@layout/shadow_statusbar_toolbar" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/status_bar" />
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/playerToolbar"
style="@style/Toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:contentInsetLeft="0dp"
app:contentInsetStart="0dp"
app:contentInsetStartWithNavigation="0dp"
app:navigationIcon="@drawable/ic_keyboard_arrow_down_black"
app:subtitleTextAppearance="@style/TextViewCaption"
app:titleMargin="0dp"
app:titleMarginStart="0dp"
app:titleTextAppearance="@style/TextViewSubtitle1"
tools:subtitle="@tools:sample/full_names"
tools:title="@tools:sample/full_names" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/playerAlbumCoverFragment"
android:name="code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="@layout/fragment_album_full_card_cover" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/cover_lyrics"
android:name="code.name.monkey.retromusic.fragments.player.CoverLyricsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:elevation="20dp" />
</FrameLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/playbackControlsFragment"
android:name="code.name.monkey.retromusic.fragments.player.adaptive.AdaptivePlaybackControlsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
tools:layout="@layout/fragment_adaptive_player_playback_controls" />
</LinearLayout>
</LinearLayout>
</FrameLayout>

View file

@ -1,81 +0,0 @@
<?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"
android:clickable="true"
android:focusable="true">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/colorBackground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
app:srcCompat="@color/black_color" />
<View
android:id="@+id/mask"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/shadow_up_strong" />
<include layout="@layout/shadow_statusbar_toolbar" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include layout="@layout/status_bar" />
</FrameLayout>
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/playerToolbar"
style="@style/Toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0"
app:contentInsetLeft="0dp"
app:contentInsetStart="0dp"
app:contentInsetStartWithNavigation="0dp"
app:navigationIcon="@drawable/ic_keyboard_arrow_down_black"
app:subtitleTextAppearance="@style/TextAppearance.AppCompat.Caption"
app:titleMargin="0dp"
app:titleMarginStart="0dp"
app:titleTextAppearance="@style/TextAppearance.AppCompat.Subhead" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="64dp"
android:layout_marginEnd="64dp"
android:layout_weight="1">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/playerAlbumCoverFragment"
android:name="code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:layout="@layout/fragment_album_card_cover" />
</FrameLayout>
</LinearLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/playbackControlsFragment"
android:name="code.name.monkey.retromusic.fragments.player.cardblur.CardBlurPlaybackControlsFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginStart="96dp"
android:layout_marginEnd="96dp"
tools:layout="@layout/fragment_card_blur_player_playback_controls" />
</FrameLayout>

View file

@ -112,7 +112,9 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:background="?attr/roundSelector"
android:padding="16dp" android:padding="16dp"
android:scaleType="fitCenter"
app:layout_constraintBottom_toBottomOf="@+id/playPauseButton" app:layout_constraintBottom_toBottomOf="@+id/playPauseButton"
app:layout_constraintStart_toEndOf="@+id/playPauseButton" app:layout_constraintStart_toEndOf="@+id/playPauseButton"
app:layout_constraintTop_toTopOf="@+id/playPauseButton" app:layout_constraintTop_toTopOf="@+id/playPauseButton"
@ -124,7 +126,9 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:background="?attr/roundSelector"
android:padding="16dp" android:padding="16dp"
android:scaleType="fitCenter"
app:layout_constraintBottom_toBottomOf="@+id/playPauseButton" app:layout_constraintBottom_toBottomOf="@+id/playPauseButton"
app:layout_constraintEnd_toStartOf="@+id/playPauseButton" app:layout_constraintEnd_toStartOf="@+id/playPauseButton"
app:layout_constraintTop_toTopOf="@+id/playPauseButton" app:layout_constraintTop_toTopOf="@+id/playPauseButton"
@ -195,7 +199,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:gravity="center_vertical|right|end" android:gravity="center_vertical|end"
android:singleLine="true" android:singleLine="true"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp" android:textSize="12sp"
@ -209,9 +213,11 @@
android:id="@+id/songCurrentProgress" android:id="@+id/songCurrentProgress"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentLeft="true" android:layout_alignParentStart="true"
android:gravity="center_vertical|left|end" android:layout_marginStart="16dp"
android:paddingLeft="8dp" android:gravity="center_vertical|start"
android:paddingStart="8dp"
android:paddingEnd="0dp"
android:singleLine="true" android:singleLine="true"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp" android:textSize="12sp"

View file

@ -10,7 +10,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<com.google.android.material.navigationrail.NavigationRailView <code.name.monkey.retromusic.views.TintedNavigationRailView
android:id="@+id/navigationView" android:id="@+id/navigationView"
style="@style/Widget.Material3.NavigationRailView" style="@style/Widget.Material3.NavigationRailView"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View file

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fitsSystemWindows="true"> android:fitsSystemWindows="true">
@ -31,52 +30,5 @@
android:overScrollMode="@integer/overScrollMode" android:overScrollMode="@integer/overScrollMode"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/donation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="@string/donation_header"
android:textAppearance="@style/TextViewSubtitle2"
android:textColor="?attr/colorAccent" />
<LinearLayout
android:id="@+id/progressContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:padding="12dp">
<ProgressBar
android:id="@+id/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true" />
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="start"
android:paddingLeft="16dp"
android:text="@string/loading_products"
android:textAppearance="@style/TextViewCaption"
tools:ignore="RtlHardcoded,RtlSymmetry" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="6dp"
android:paddingEnd="6dp"
android:scrollbarStyle="outsideOverlay" />
</LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -4,7 +4,9 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?colorSurface"> android:background="?colorSurface"
android:clickable="true"
android:focusable="true">
<include layout="@layout/shadow_statusbar_toolbar" /> <include layout="@layout/shadow_statusbar_toolbar" />

View file

@ -25,45 +25,58 @@
<include layout="@layout/shadow_statusbar_toolbar" /> <include layout="@layout/shadow_statusbar_toolbar" />
<FrameLayout
android:id="@+id/cardContainer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
<include layout="@layout/status_bar" /> <include layout="@layout/status_bar" />
<com.google.android.material.appbar.MaterialToolbar <com.google.android.material.textview.MaterialTextView
android:id="@+id/playerToolbar" android:id="@+id/title"
style="@style/Toolbar" android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="0" android:layout_marginTop="16dp"
app:contentInsetLeft="0dp" android:clickable="true"
app:contentInsetStart="0dp" android:ellipsize="end"
app:contentInsetStartWithNavigation="0dp" android:focusable="true"
app:navigationIcon="@drawable/ic_keyboard_arrow_down_black" android:freezesText="true"
app:subtitleTextAppearance="@style/TextAppearance.AppCompat.Caption" android:paddingHorizontal="24dp"
app:titleMargin="0dp" android:scrollHorizontally="true"
app:titleMarginStart="0dp" android:singleLine="true"
app:titleTextAppearance="@style/TextAppearance.AppCompat.Subhead" /> android:textAppearance="@style/TextViewHeadline6"
android:textColor="?android:attr/textColorPrimary"
android:textStyle="bold"
tools:text="@tools:sample/lorem" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:clickable="true"
android:ellipsize="end"
android:focusable="true"
android:freezesText="true"
android:paddingHorizontal="24dp"
android:scrollHorizontally="true"
android:singleLine="true"
android:textAppearance="@style/TextViewBody1"
android:textColor="?android:attr/textColorPrimary"
tools:text="@tools:sample/lorem" />
<FrameLayout <FrameLayout
android:id="@+id/cardContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="0dp"
android:layout_gravity="center_horizontal"
android:layout_weight="1"> android:layout_weight="1">
<androidx.fragment.app.FragmentContainerView <androidx.fragment.app.FragmentContainerView
android:id="@+id/playerAlbumCoverFragment" android:id="@+id/playerAlbumCoverFragment"
android:name="code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment" android:name="code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
tools:layout="@layout/fragment_album_card_cover" /> tools:layout="@layout/fragment_album_full_card_cover" />
<androidx.fragment.app.FragmentContainerView <androidx.fragment.app.FragmentContainerView
android:id="@+id/cover_lyrics" android:id="@+id/cover_lyrics"
@ -72,9 +85,6 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center" android:layout_gravity="center"
tools:layout="@layout/fragment_cover_lyrics" /> tools:layout="@layout/fragment_cover_lyrics" />
</FrameLayout>
</LinearLayout>
<androidx.fragment.app.FragmentContainerView <androidx.fragment.app.FragmentContainerView
android:id="@+id/playbackControlsFragment" android:id="@+id/playbackControlsFragment"
@ -85,4 +95,13 @@
tools:layout="@layout/fragment_card_blur_player_playback_controls" /> tools:layout="@layout/fragment_card_blur_player_playback_controls" />
</FrameLayout> </FrameLayout>
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/playerToolbar"
style="@style/Toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:navigationIcon="@drawable/ic_keyboard_arrow_down_black" />
</LinearLayout>
</FrameLayout> </FrameLayout>

View file

@ -21,7 +21,7 @@
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:gravity="center" android:gravity="center"
android:shadowColor="@color/md_black_1000" android:shadowColor="@color/md_black_1000"
android:shadowRadius="4" android:shadowRadius="10"
android:textAppearance="@style/TextViewHeadline5" android:textAppearance="@style/TextViewHeadline5"
android:textColor="@color/md_white_1000" android:textColor="@color/md_white_1000"
android:visibility="gone" android:visibility="gone"
@ -34,7 +34,7 @@
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:gravity="center" android:gravity="center"
android:shadowColor="@color/md_black_1000" android:shadowColor="@color/md_black_1000"
android:shadowRadius="4" android:shadowRadius="10"
android:textAppearance="@style/TextViewHeadline5" android:textAppearance="@style/TextViewHeadline5"
android:textColor="@color/md_white_1000" android:textColor="@color/md_white_1000"
tools:text="@tools:sample/full_names[5]" /> tools:text="@tools:sample/full_names[5]" />

View file

@ -127,8 +127,9 @@
android:name="code.name.monkey.retromusic.fragments.player.CoverLyricsFragment" android:name="code.name.monkey.retromusic.fragments.player.CoverLyricsFragment"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:elevation="20dp" android:elevation="20dp"
app:layout_constraintBottom_toTopOf="@+id/playbackControlsFragment"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent" />
app:layout_constraintTop_toBottomOf="@id/playerToolbar" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -58,7 +58,8 @@
app:layout_constraintBottom_toBottomOf="@+id/playerAlbumCoverFragment" app:layout_constraintBottom_toBottomOf="@+id/playerAlbumCoverFragment"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/status_bar" /> android:layout_marginBottom="16dp"
tools:layout="@layout/fragment_cover_lyrics" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -32,33 +32,52 @@
app:contentInsetStart="0dp" app:contentInsetStart="0dp"
app:contentInsetStartWithNavigation="0dp" app:contentInsetStartWithNavigation="0dp"
app:navigationIcon="@drawable/ic_keyboard_backspace_black" app:navigationIcon="@drawable/ic_keyboard_backspace_black"
app:subtitleTextAppearance="@style/TextViewCaption" app:title="@string/lyrics"
app:titleMargin="0dp" app:titleMargin="0dp"
app:titleMarginStart="0dp" app:titleMarginStart="0dp"
app:titleTextAppearance="@style/TextViewSubtitle1"> app:titleTextAppearance="@style/ToolbarTextAppearanceNormal" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLyrics"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tabIndicator="@drawable/tab_lyrics_indicator"
app:tabIndicatorAnimationMode="elastic"
app:tabIndicatorFullWidth="false"
app:tabIndicatorHeight="5dp" />
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager2.widget.ViewPager2 <FrameLayout
android:id="@+id/lyricsPager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginTop="?attr/actionBarSize" android:layout_marginTop="?attr/actionBarSize">
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" <ScrollView
app:layout_constraintStart_toStartOf="parent" /> android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical">
<TextView
android:id="@+id/normalLyrics"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/normal_lyrics_padding"
android:textIsSelectable="true"
android:textSize="@dimen/lyrics_text_size"
tools:text="@tools:sample/lorem[100]" />
</ScrollView>
<code.name.monkey.retromusic.lyrics.LrcView
android:id="@+id/lyricsView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
app:lrcLabel="@string/no_lyrics_found"
app:lrcNormalTextSize="24sp"
app:lrcPadding="24dp"
app:lrcTextGravity="left"
app:lrcTextSize="28sp" />
<TextView
android:id="@+id/noLyricsFound"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/no_lyrics_found"
android:textSize="24sp"
android:visibility="gone" />
</FrameLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/edit_button" android:id="@+id/edit_button"

View file

@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:id="@+id/nomal_lyrics_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical">
<TextView
android:id="@+id/normalLyrics"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/normal_lyrics_padding"
android:textIsSelectable="true"
android:textSize="@dimen/lyrics_text_size" />
</ScrollView>
<TextView
android:id="@+id/noLyricsFound"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/no_lyrics_found"
android:textSize="24sp"
android:visibility="gone" />
</FrameLayout>

View file

@ -1,18 +0,0 @@
<?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"
android:id="@+id/synced_lyrics_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<code.name.monkey.retromusic.lyrics.LrcView
android:id="@+id/lyricsView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
app:lrcLabel="@string/no_lyrics_found"
app:lrcNormalTextSize="24sp"
app:lrcPadding="24dp"
app:lrcTextGravity="left"
app:lrcTextSize="28sp" />
</FrameLayout>

View file

@ -42,7 +42,7 @@
</FrameLayout> </FrameLayout>
<code.name.monkey.retromusic.views.BottomNavigationBarTinted <code.name.monkey.retromusic.views.TintedBottomNavigationView
android:id="@+id/navigationView" android:id="@+id/navigationView"
style="@style/Widget.Material3.BottomNavigationView" style="@style/Widget.Material3.BottomNavigationView"
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -125,5 +125,5 @@
<fragment <fragment
android:id="@+id/lyrics_fragment" android:id="@+id/lyrics_fragment"
android:name="code.name.monkey.retromusic.fragments.other.LyricsFragment" /> android:name="code.name.monkey.retromusic.fragments.lyrics.LyricsFragment" />
</navigation> </navigation>

View file

@ -56,14 +56,6 @@
android:title="@string/pref_title_toggle_toggle_headset" android:title="@string/pref_title_toggle_toggle_headset"
app:icon="@drawable/ic_play_arrow" /> app:icon="@drawable/ic_play_arrow" />
<code.name.monkey.appthemehelper.common.prefs.supportv7.ATESwitchPreference
android:defaultValue="false"
android:key="toggle_shuffle"
android:layout="@layout/list_item_view_switch"
android:summary="@string/pref_summary_toggle_shuffle"
android:title="@string/pref_title_toggle_toggle_shuffle"
app:icon="@drawable/ic_shuffle" />
<code.name.monkey.appthemehelper.common.prefs.supportv7.ATESwitchPreference <code.name.monkey.appthemehelper.common.prefs.supportv7.ATESwitchPreference
android:defaultValue="false" android:defaultValue="false"
android:key="bluetooth_playback" android:key="bluetooth_playback"

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="code.name.monkey.retromusic">
<uses-permission android:name="com.android.vending.BILLING" />
<application tools:ignore="MissingApplicationIcon">
<activity android:name=".activities.PurchaseActivity" />
<meta-data
android:name="com.android.vending.splits.required"
android:value="true" />
<!-- ChromeCast -->
<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="code.name.monkey.retromusic.cast.CastOptionsProvider" />
</application>
</manifest>

View file

@ -1,17 +1,3 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.activities package code.name.monkey.retromusic.activities
import android.content.res.ColorStateList import android.content.res.ColorStateList
@ -22,7 +8,7 @@ import android.view.MenuItem
import code.name.monkey.appthemehelper.util.MaterialUtil import code.name.monkey.appthemehelper.util.MaterialUtil
import code.name.monkey.retromusic.App import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.BuildConfig import code.name.monkey.retromusic.BuildConfig
import code.name.monkey.retromusic.Constants.PRO_VERSION_PRODUCT_ID import code.name.monkey.retromusic.Constants
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsThemeActivity import code.name.monkey.retromusic.activities.base.AbsThemeActivity
import code.name.monkey.retromusic.databinding.ActivityProVersionBinding import code.name.monkey.retromusic.databinding.ActivityProVersionBinding
@ -58,7 +44,7 @@ class PurchaseActivity : AbsThemeActivity(), BillingProcessor.IBillingHandler {
restorePurchase() restorePurchase()
} }
binding.purchaseButton.setOnClickListener { binding.purchaseButton.setOnClickListener {
billingProcessor.purchase(this@PurchaseActivity, PRO_VERSION_PRODUCT_ID) billingProcessor.purchase(this@PurchaseActivity, Constants.PRO_VERSION_PRODUCT_ID)
} }
binding.bannerContainer.backgroundTintList = binding.bannerContainer.backgroundTintList =
ColorStateList.valueOf(accentColor()) ColorStateList.valueOf(accentColor())

View file

@ -3,6 +3,7 @@ package code.name.monkey.retromusic.activities.base
import code.name.monkey.retromusic.cast.RetroSessionManagerListener import code.name.monkey.retromusic.cast.RetroSessionManagerListener
import code.name.monkey.retromusic.cast.RetroWebServer import code.name.monkey.retromusic.cast.RetroWebServer
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.service.CastPlayer
import com.google.android.gms.cast.framework.CastContext import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.CastSession import com.google.android.gms.cast.framework.CastSession
import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.ConnectionResult
@ -37,7 +38,7 @@ abstract class AbsCastActivity : AbsSlidingMusicPanelActivity() {
override fun onSessionStarted(castSession: CastSession, p1: String) { override fun onSessionStarted(castSession: CastSession, p1: String) {
invalidateOptionsMenu() invalidateOptionsMenu()
mCastSession = castSession mCastSession = castSession
MusicPlayerRemote.switchToRemotePlayback(castSession) MusicPlayerRemote.switchToRemotePlayback(CastPlayer(castSession))
} }
override fun onSessionEnded(castSession: CastSession, p1: Int) { override fun onSessionEnded(castSession: CastSession, p1: Int) {
@ -53,7 +54,7 @@ abstract class AbsCastActivity : AbsSlidingMusicPanelActivity() {
invalidateOptionsMenu() invalidateOptionsMenu()
mCastSession = castSession mCastSession = castSession
webServer.start() webServer.start()
MusicPlayerRemote.switchToRemotePlayback(castSession) MusicPlayerRemote.switchToRemotePlayback(CastPlayer(castSession))
} }
override fun onSessionSuspended(castSession: CastSession, p1: Int) { override fun onSessionSuspended(castSession: CastSession, p1: Int) {

View file

@ -0,0 +1,37 @@
package code.name.monkey.retromusic.billing
import android.content.Context
import code.name.monkey.retromusic.BuildConfig
import code.name.monkey.retromusic.Constants
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.showToast
import com.anjlab.android.iab.v3.BillingProcessor
import com.anjlab.android.iab.v3.PurchaseInfo
class BillingManager(context: Context) {
private val billingProcessor: BillingProcessor
init {
// automatically restores purchases
billingProcessor = BillingProcessor(
context, BuildConfig.GOOGLE_PLAY_LICENSING_KEY,
object : BillingProcessor.IBillingHandler {
override fun onProductPurchased(productId: String, details: PurchaseInfo?) {}
override fun onPurchaseHistoryRestored() {
context.showToast(R.string.restored_previous_purchase_please_restart)
}
override fun onBillingError(errorCode: Int, error: Throwable?) {}
override fun onBillingInitialized() {}
})
}
fun release() {
billingProcessor.release()
}
val isProVersion: Boolean
get() = billingProcessor.isPurchased(Constants.PRO_VERSION_PRODUCT_ID)
}

View file

@ -0,0 +1,25 @@
@file:Suppress("unused")
package code.name.monkey.retromusic.cast
import android.content.Context
import com.google.android.gms.cast.CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID
import com.google.android.gms.cast.framework.CastOptions
import com.google.android.gms.cast.framework.OptionsProvider
import com.google.android.gms.cast.framework.SessionProvider
import com.google.android.gms.cast.framework.media.CastMediaOptions
class CastOptionsProvider : OptionsProvider {
override fun getCastOptions(context: Context): CastOptions {
val mediaOptions = CastMediaOptions.Builder().setNotificationOptions(null).build()
return CastOptions.Builder()
.setReceiverApplicationId(DEFAULT_MEDIA_RECEIVER_APPLICATION_ID)
.setCastMediaOptions(mediaOptions)
.build()
}
override fun getAdditionalSessionProviders(context: Context): MutableList<SessionProvider>? {
return null
}
}

View file

@ -0,0 +1,59 @@
package code.name.monkey.retromusic.extensions
import android.content.Context
import android.content.Intent
import android.view.Menu
import androidx.fragment.app.FragmentActivity
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.PurchaseActivity
import com.google.android.gms.cast.framework.CastButtonFactory
import com.google.android.play.core.splitcompat.SplitCompat
import com.google.android.play.core.splitinstall.SplitInstallManagerFactory
import com.google.android.play.core.splitinstall.SplitInstallRequest
import com.google.android.play.core.splitinstall.SplitInstallSessionState
import com.google.android.play.core.splitinstall.SplitInstallStateUpdatedListener
import java.util.*
fun Context.setUpMediaRouteButton(menu: Menu) {
CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.action_cast)
}
fun FragmentActivity.installLanguageAndRecreate(code: String) {
var mySessionId = 0
val manager = SplitInstallManagerFactory.create(this)
val listener = object: SplitInstallStateUpdatedListener{
override fun onStateUpdate(state: SplitInstallSessionState) {
if (state.sessionId() == mySessionId) {
recreate()
manager.unregisterListener(this)
}
}
}
manager.registerListener(listener)
if (code != "auto") {
// Try to download language resources
val request =
SplitInstallRequest.newBuilder().addLanguage(Locale.forLanguageTag(code))
.build()
manager.startInstall(request)
// Recreate the activity on download complete
.addOnSuccessListener {
mySessionId = it
}
.addOnFailureListener {
showToast("Language download failed.")
}
} else {
recreate()
}
}
fun Context.goToProVersion() {
startActivity(Intent(this, PurchaseActivity::class.java))
}
fun Context.installSplitCompat() {
SplitCompat.install(this)
}

View file

@ -10,8 +10,7 @@ import com.google.android.gms.cast.MediaStatus
import com.google.android.gms.cast.framework.CastSession import com.google.android.gms.cast.framework.CastSession
import com.google.android.gms.cast.framework.media.RemoteMediaClient import com.google.android.gms.cast.framework.media.RemoteMediaClient
class CastPlayer(castSession: CastSession) : Playback, class CastPlayer(castSession: CastSession) : Playback, RemoteMediaClient.Callback() {
RemoteMediaClient.Callback() {
override val isInitialized: Boolean = true override val isInitialized: Boolean = true
@ -33,15 +32,19 @@ class CastPlayer(castSession: CastSession) : Playback,
override var callbacks: Playback.PlaybackCallbacks? = null override var callbacks: Playback.PlaybackCallbacks? = null
override fun setDataSource(song: Song, force: Boolean): Boolean { override fun setDataSource(
return try { song: Song,
force: Boolean,
completion: (success: Boolean) -> Unit,
) {
try {
val mediaLoadOptions = val mediaLoadOptions =
MediaLoadOptions.Builder().setPlayPosition(0).setAutoplay(true).build() MediaLoadOptions.Builder().setPlayPosition(0).setAutoplay(true).build()
remoteMediaClient?.load(song.toMediaInfo()!!, mediaLoadOptions) remoteMediaClient?.load(song.toMediaInfo()!!, mediaLoadOptions)
true completion(true)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
false completion(false)
} }
} }

View file

@ -28,7 +28,6 @@ object AppRater {
private const val DAYS_UNTIL_PROMPT = 3//Min number of days private const val DAYS_UNTIL_PROMPT = 3//Min number of days
private const val LAUNCHES_UNTIL_PROMPT = 5//Min number of launches private const val LAUNCHES_UNTIL_PROMPT = 5//Min number of launches
@JvmStatic
fun appLaunched(context: Activity) { fun appLaunched(context: Activity) {
val prefs = context.getSharedPreferences(APP_RATING, 0) val prefs = context.getSharedPreferences(APP_RATING, 0)
if (prefs.getBoolean(DO_NOT_SHOW_AGAIN, false)) { if (prefs.getBoolean(DO_NOT_SHOW_AGAIN, false)) {

View file

@ -1,52 +0,0 @@
package code.name.monkey.appthemehelper.util
import android.content.res.ColorStateList
import androidx.annotation.ColorInt
import code.name.monkey.appthemehelper.ThemeStore
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.navigation.NavigationView
/**
* @author Karim Abou Zeid (kabouzeid)
*/
object NavigationViewUtil {
fun setItemIconColors(navigationView: NavigationView, @ColorInt normalColor: Int, @ColorInt selectedColor: Int) {
val iconSl = ColorStateList(
arrayOf(
intArrayOf(-android.R.attr.state_checked),
intArrayOf(android.R.attr.state_checked)
), intArrayOf(normalColor, selectedColor)
)
navigationView.itemIconTintList = iconSl
val drawable = navigationView.itemBackground
navigationView.itemBackground = TintHelper.createTintedDrawable(
drawable,
ColorUtil.withAlpha(ThemeStore.accentColor(navigationView.context), 0.2f)
)
}
fun setItemTextColors(navigationView: NavigationView, @ColorInt normalColor: Int, @ColorInt selectedColor: Int) {
val textSl = ColorStateList(
arrayOf(intArrayOf(-android.R.attr.state_checked), intArrayOf(android.R.attr.state_checked)),
intArrayOf(normalColor, selectedColor)
)
navigationView.itemTextColor = textSl
}
fun setItemIconColors(bottomNavigationView: BottomNavigationView, @ColorInt normalColor: Int, @ColorInt selectedColor: Int) {
val iconSl = ColorStateList(
arrayOf(intArrayOf(-android.R.attr.state_checked), intArrayOf(android.R.attr.state_checked)),
intArrayOf(normalColor, selectedColor)
)
bottomNavigationView.itemIconTintList = iconSl
}
fun setItemTextColors(bottomNavigationView: BottomNavigationView, @ColorInt normalColor: Int, @ColorInt selectedColor: Int) {
val textSl = ColorStateList(
arrayOf(intArrayOf(-android.R.attr.state_checked), intArrayOf(android.R.attr.state_checked)),
intArrayOf(normalColor, selectedColor)
)
bottomNavigationView.itemTextColor = textSl
}
}

View file

@ -2,11 +2,13 @@
buildscript { buildscript {
ext { ext {
kotlin_version = '1.6.21' kotlin_version = '1.7.0'
navigation_version = '2.5.0-rc01' lifecycle_version='2.5.0-rc02'
navigation_version = '2.5.0-rc02'
mdc_version = '1.7.0-alpha02' mdc_version = '1.7.0-alpha02'
preference_version = '1.2.0' preference_version = '1.2.0'
appcompat_version = '1.4.2' appcompat_version = '1.4.2'
core_version='1.8.0'
} }
repositories { repositories {

View file

@ -0,0 +1 @@
Added lyrics downloading

View file

@ -0,0 +1,20 @@
Retro Music Player 🎵
<b>📦 Included Features</b>
<ul>
<li>Base 3 themes (Clearly White, Kinda Dark and Just Black)</li>
<li>Material You support on Android 12+</li>
<li>Gapless playback</li>
<li>Crossfade playback</li>
<li>Choose from 10+ now playing themes</li>
<li>Android auto support</li>
<li>Wallpaper accent picker on Android 8.1+</li>
<li>Home screen widgets</li>
<li>Lock screen playback controls</li>
<li>Sleep timer</li>
<li>Easy drag to sort playlist & play queue</li>
<li>Tag editor</li>
<li>Create, edit and import playlists</li>
<li>Browse and play your music by songs, albums, artists, playlists and genre</li>
<li>Smart Auto Playlists - Recently played, most played and history</li>
</ul>

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 578 KiB

After

Width:  |  Height:  |  Size: 578 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 692 KiB

After

Width:  |  Height:  |  Size: 692 KiB

Before After
Before After

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