Merge branch 'dev' into l10n_dev
This commit is contained in:
commit
26d7012e46
111 changed files with 2010 additions and 2159 deletions
|
@ -38,12 +38,12 @@ ___
|
|||
|
||||
## 📱 Screenshots
|
||||
### 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|
|
||||
|
||||
### 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 |
|
||||
|
||||
|
@ -58,7 +58,7 @@ ___
|
|||
| Synced Replace Cover light | Synced Replace Cover dark | Synced Replace Cover black |
|
||||
|
||||
### 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 |
|
||||
|
||||
|
|
|
@ -14,8 +14,8 @@ android {
|
|||
vectorDrawables.useSupportLibrary = true
|
||||
|
||||
applicationId "code.name.monkey.retromusic"
|
||||
versionCode 10590
|
||||
versionName '6.0.0-beta'
|
||||
versionCode 10596
|
||||
versionName '6.0.2-beta'
|
||||
|
||||
buildConfigField("String", "GOOGLE_PLAY_LICENSING_KEY", "\"${getProperty(getProperties('../public.properties'), 'GOOGLE_PLAY_LICENSE_KEY')}\"")
|
||||
}
|
||||
|
@ -41,6 +41,15 @@ android {
|
|||
versionNameSuffix ' DEBUG'
|
||||
}
|
||||
}
|
||||
flavorDimensions "version"
|
||||
productFlavors {
|
||||
normal {
|
||||
dimension "version"
|
||||
}
|
||||
fdroid {
|
||||
dimension "version"
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures{
|
||||
viewBinding true
|
||||
|
@ -88,44 +97,43 @@ dependencies {
|
|||
implementation "androidx.gridlayout:gridlayout:1.0.0"
|
||||
|
||||
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.recyclerview:recyclerview:1.2.1'
|
||||
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'
|
||||
|
||||
//Cast Dependencies
|
||||
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
|
||||
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-fragment-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-ktx:$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-livedata-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
||||
|
||||
implementation "androidx.core:core-splashscreen:1.0.0-rc01"
|
||||
|
||||
implementation 'com.google.android.play:feature-delivery:2.0.0'
|
||||
implementation 'com.google.android.play:review:2.0.0'
|
||||
normalImplementation 'com.google.android.play:feature-delivery:2.0.0'
|
||||
normalImplementation 'com.google.android.play:review:2.0.0'
|
||||
|
||||
implementation "com.google.android.material:material:$mdc_version"
|
||||
|
||||
def retrofit_version = '2.9.0'
|
||||
implementation "com.squareup.retrofit2:retrofit:$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"
|
||||
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 '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.heinrichreimersoftware:material-intro:2.0.0'
|
||||
implementation 'com.github.dhaval2404:imagepicker:2.1'
|
||||
implementation 'me.zhanghai.android.fastscroll:library:1.1.8'
|
||||
implementation 'cat.ereza:customactivityoncrash:2.4.0'
|
||||
implementation 'me.tankery.lib:circularSeekBar:1.4.0'
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package code.name.monkey.retromusic.activities.base
|
||||
|
||||
|
||||
abstract class AbsCastActivity : AbsSlidingMusicPanelActivity() {
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package code.name.monkey.retromusic.cast
|
||||
|
||||
import android.content.Context
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
class RetroWebServer(context: Context)
|
|
@ -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() {}
|
|
@ -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) {}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package code.name.monkey.retromusic.util
|
||||
|
||||
import android.content.Context
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
object AppRater {
|
||||
fun appLaunched(context: Context) {}
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission
|
||||
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.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
|
||||
|
@ -22,7 +22,6 @@
|
|||
android:name="android.permission.WRITE_SETTINGS"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="com.android.vending.BILLING" />
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH_CONNECT"
|
||||
android:usesPermissionFlags="neverForLocation"
|
||||
|
@ -34,12 +33,12 @@
|
|||
android:configChanges="locale|layoutDirection"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:restoreAnyVersion="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.RetroMusic.FollowSystem"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
tools:targetApi="m">
|
||||
<activity
|
||||
android:name=".activities.MainActivity"
|
||||
|
@ -120,7 +119,6 @@
|
|||
<activity android:name=".activities.tageditor.SongTagEditorActivity" />
|
||||
<activity android:name=".activities.SupportDevelopmentActivity" />
|
||||
<activity android:name=".activities.LicenseActivity" />
|
||||
<activity android:name=".activities.PurchaseActivity" />
|
||||
<activity android:name=".activities.bugreport.BugReportActivity" />
|
||||
<activity android:name=".activities.ShareInstagramStory" />
|
||||
<activity android:name=".activities.DriveModeActivity" />
|
||||
|
@ -194,7 +192,7 @@
|
|||
</provider>
|
||||
|
||||
<receiver
|
||||
android:name="androidx.media.session.MediaButtonReceiver"
|
||||
android:name=".service.MediaButtonIntentReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||
|
@ -305,9 +303,6 @@
|
|||
<intent-filter>
|
||||
<action android:name="android.media.browse.MediaBrowserService" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<meta-data
|
||||
|
@ -318,10 +313,6 @@
|
|||
android:name="com.lge.support.SPLIT_WINDOW"
|
||||
android:value="true" />
|
||||
|
||||
<meta-data
|
||||
android:name="com.android.vending.splits.required"
|
||||
android:value="true" />
|
||||
|
||||
<!-- Android Auto -->
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.car.application"
|
||||
|
@ -332,11 +323,6 @@
|
|||
<meta-data
|
||||
android:name="com.google.android.gms.car.notification.SmallIcon"
|
||||
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>
|
||||
|
||||
<!--
|
||||
|
|
|
@ -62,6 +62,25 @@
|
|||
</head>
|
||||
|
||||
<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>
|
||||
<h5>June 7, 2022</h5>
|
||||
<h2>v6.0.0<span class="tag"><i>Beta</i></span></h2>
|
||||
|
|
|
@ -19,20 +19,17 @@ import androidx.preference.PreferenceManager
|
|||
import cat.ereza.customactivityoncrash.config.CaocConfig
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
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.MainActivity
|
||||
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 com.anjlab.android.iab.v3.BillingProcessor
|
||||
import com.anjlab.android.iab.v3.PurchaseInfo
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.core.context.startKoin
|
||||
|
||||
class App : Application() {
|
||||
|
||||
lateinit var billingProcessor: BillingProcessor
|
||||
lateinit var billingManager: BillingManager
|
||||
private val wallpaperAccentManager = WallpaperAccentManager(this)
|
||||
|
||||
override fun onCreate() {
|
||||
|
@ -55,33 +52,20 @@ class App : Application() {
|
|||
if (VersionUtils.hasNougatMR())
|
||||
DynamicShortcutManager(this).initDynamicShortcuts()
|
||||
|
||||
// automatically restores purchases
|
||||
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() {}
|
||||
})
|
||||
billingManager = BillingManager(this)
|
||||
|
||||
// setting Error activity
|
||||
CaocConfig.Builder.create().errorActivity(ErrorActivity::class.java)
|
||||
.restartActivity(MainActivity::class.java).apply()
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
override fun onTerminate() {
|
||||
super.onTerminate()
|
||||
billingProcessor.release()
|
||||
billingManager.release()
|
||||
wallpaperAccentManager.release()
|
||||
}
|
||||
|
||||
|
@ -93,9 +77,7 @@ class App : Application() {
|
|||
}
|
||||
|
||||
fun isProVersion(): Boolean {
|
||||
return BuildConfig.DEBUG || instance?.billingProcessor!!.isPurchased(
|
||||
PRO_VERSION_PRODUCT_ID
|
||||
)
|
||||
return BuildConfig.DEBUG || instance?.billingManager!!.isProVersion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 CHOOSE_EQUALIZER = "choose_equalizer"
|
||||
const val EQUALIZER = "equalizer"
|
||||
const val TOGGLE_SHUFFLE = "toggle_shuffle"
|
||||
const val SONG_GRID_STYLE = "song_grid_style"
|
||||
const val PAUSE_ON_ZERO_VOLUME = "pause_on_zero_volume"
|
||||
const val FILTER_SONG = "filter_song"
|
||||
|
|
|
@ -14,12 +14,10 @@
|
|||
*/
|
||||
package code.name.monkey.retromusic.activities
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.graphics.PorterDuff
|
||||
import android.os.Bundle
|
||||
import android.view.animation.LinearInterpolator
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import code.name.monkey.retromusic.R
|
||||
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.extensions.accentColor
|
||||
import code.name.monkey.retromusic.extensions.drawAboveSystemBars
|
||||
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
|
||||
import code.name.monkey.retromusic.glide.BlurTransformation
|
||||
import code.name.monkey.retromusic.glide.GlideApp
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||
|
@ -246,12 +243,10 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
|
|||
}
|
||||
|
||||
override fun onUpdateProgressViews(progress: Int, total: Int) {
|
||||
binding.progressSlider.valueTo = total.toFloat()
|
||||
|
||||
val animator = ObjectAnimator.ofFloat(binding.progressSlider, "value", progress.toFloat())
|
||||
animator.duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME
|
||||
animator.interpolator = LinearInterpolator()
|
||||
animator.start()
|
||||
binding.progressSlider.run {
|
||||
valueTo = total.toFloat()
|
||||
value = progress.toFloat().coerceIn(valueFrom, valueTo)
|
||||
}
|
||||
|
||||
binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
|
||||
binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
|
||||
|
|
|
@ -14,41 +14,23 @@
|
|||
*/
|
||||
package code.name.monkey.retromusic.activities
|
||||
|
||||
import android.graphics.Paint
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
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.retromusic.BuildConfig
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.activities.base.AbsThemeActivity
|
||||
import code.name.monkey.retromusic.databinding.ActivityDonationBinding
|
||||
import code.name.monkey.retromusic.databinding.ItemDonationOptionBinding
|
||||
import code.name.monkey.retromusic.extensions.*
|
||||
import com.anjlab.android.iab.v3.BillingProcessor
|
||||
import com.anjlab.android.iab.v3.PurchaseInfo
|
||||
import com.anjlab.android.iab.v3.SkuDetails
|
||||
import code.name.monkey.retromusic.extensions.setStatusBarColorAuto
|
||||
import code.name.monkey.retromusic.extensions.setTaskDescriptionColorAuto
|
||||
import code.name.monkey.retromusic.extensions.surfaceColor
|
||||
|
||||
class SupportDevelopmentActivity : AbsThemeActivity(), BillingProcessor.IBillingHandler {
|
||||
class SupportDevelopmentActivity : AbsThemeActivity() {
|
||||
|
||||
lateinit var binding: ActivityDonationBinding
|
||||
|
||||
companion object {
|
||||
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 {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
onBackPressed()
|
||||
|
@ -57,11 +39,6 @@ class SupportDevelopmentActivity : AbsThemeActivity(), BillingProcessor.IBilling
|
|||
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?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityDonationBinding.inflate(layoutInflater)
|
||||
|
@ -72,148 +49,11 @@ class SupportDevelopmentActivity : AbsThemeActivity(), BillingProcessor.IBilling
|
|||
|
||||
setupToolbar()
|
||||
|
||||
billingProcessor = BillingProcessor(this, BuildConfig.GOOGLE_PLAY_LICENSING_KEY, this)
|
||||
TintHelper.setTint(binding.progress, accentColor())
|
||||
binding.donation.setTextColor(accentColor())
|
||||
}
|
||||
|
||||
private fun setupToolbar() {
|
||||
val toolbarColor = surfaceColor()
|
||||
binding.toolbar.setBackgroundColor(toolbarColor)
|
||||
binding.toolbar.setBackgroundColor(surfaceColor())
|
||||
ToolbarContentTintHelper.colorBackButton(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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -191,7 +191,7 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventLi
|
|||
|
||||
override fun getPermissionsToRequest(): Array<String> {
|
||||
return mutableListOf(Manifest.permission.READ_EXTERNAL_STORAGE).apply {
|
||||
if (!VersionUtils.hasQ()) {
|
||||
if (!VersionUtils.hasR()) {
|
||||
add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
}
|
||||
}.toTypedArray()
|
||||
|
|
|
@ -234,8 +234,7 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity(),
|
|||
navigationView.labelVisibilityMode = PreferenceUtil.tabTitleMode
|
||||
}
|
||||
TOGGLE_FULL_SCREEN -> {
|
||||
if (!PreferenceUtil.isFullScreenMode) exitFullscreen()
|
||||
setEdgeToEdgeOrImmersive()
|
||||
recreate()
|
||||
}
|
||||
SCREEN_ON_LYRICS -> {
|
||||
keepScreenOn(bottomSheetBehavior.state == STATE_EXPANDED && PreferenceUtil.lyricsScreenOn && PreferenceUtil.showLyrics || PreferenceUtil.isScreenOnEnabled)
|
||||
|
|
|
@ -31,7 +31,6 @@ import code.name.monkey.retromusic.util.PreferenceUtil
|
|||
import code.name.monkey.retromusic.util.maybeShowAnnoyingToasts
|
||||
import code.name.monkey.retromusic.util.theme.getNightMode
|
||||
import code.name.monkey.retromusic.util.theme.getThemeResValue
|
||||
import com.google.android.play.core.splitcompat.SplitCompat
|
||||
import java.util.*
|
||||
|
||||
abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable {
|
||||
|
@ -105,6 +104,6 @@ abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable {
|
|||
Locale.forLanguageTag(code)
|
||||
}
|
||||
super.attachBaseContext(LanguageContextWrapper.wrap(newBase, locale))
|
||||
SplitCompat.install(this)
|
||||
installSplitCompat()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ class PlayingQueueAdapter(
|
|||
activity: FragmentActivity,
|
||||
dataSet: MutableList<Song>,
|
||||
private var current: Int,
|
||||
itemLayoutRes: Int
|
||||
itemLayoutRes: Int,
|
||||
) : SongAdapter(
|
||||
activity, dataSet, itemLayoutRes, null
|
||||
), DraggableItemAdapter<PlayingQueueAdapter.ViewHolder>,
|
||||
|
@ -153,6 +153,14 @@ class PlayingQueueAdapter(
|
|||
dragView?.isVisible = true
|
||||
}
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
if (isInQuickSelectMode) {
|
||||
toggleChecked(layoutPosition)
|
||||
} else {
|
||||
MusicPlayerRemote.playSongAt(layoutPosition)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSongMenuItemClick(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.action_remove_from_playing_queue -> {
|
||||
|
@ -209,7 +217,7 @@ class PlayingQueueAdapter(
|
|||
internal class SwipedResultActionRemoveItem(
|
||||
private val adapter: PlayingQueueAdapter,
|
||||
private val position: Int,
|
||||
private val activity: FragmentActivity
|
||||
private val activity: FragmentActivity,
|
||||
) : SwipeResultActionRemoveItem() {
|
||||
|
||||
private var songToRemove: Song? = null
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -95,7 +95,7 @@ class SleepTimerDialog : DialogFragment() {
|
|||
shouldFinishLastSong.isVisible = false
|
||||
timerUpdater.start()
|
||||
setPositiveButton(android.R.string.ok, null)
|
||||
setNegativeButton(R.string.cast_stop) { _, _ ->
|
||||
setNegativeButton(R.string.action_cancel) { _, _ ->
|
||||
timerUpdater.cancel()
|
||||
val previous = makeTimerPendingIntent(PendingIntent.FLAG_NO_CREATE)
|
||||
if (previous != null) {
|
||||
|
|
|
@ -14,9 +14,11 @@
|
|||
*/
|
||||
package code.name.monkey.retromusic.extensions
|
||||
|
||||
import android.R
|
||||
import android.animation.Animator
|
||||
import android.animation.ObjectAnimator
|
||||
import android.animation.ValueAnimator
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
@ -25,6 +27,7 @@ import android.view.ViewTreeObserver
|
|||
import android.view.animation.AnimationUtils
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.EditText
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.annotation.Px
|
||||
import androidx.core.animation.doOnEnd
|
||||
|
@ -67,6 +70,15 @@ fun EditText.appHandleColor(): EditText {
|
|||
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].
|
||||
*
|
||||
|
|
|
@ -24,6 +24,7 @@ import androidx.recyclerview.widget.GridLayoutManager
|
|||
import code.name.monkey.retromusic.EXTRA_ALBUM_ID
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.adapter.album.AlbumAdapter
|
||||
import code.name.monkey.retromusic.extensions.setUpMediaRouteButton
|
||||
import code.name.monkey.retromusic.extensions.surfaceColor
|
||||
import code.name.monkey.retromusic.fragments.GridStyle
|
||||
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.isActive
|
||||
import com.afollestad.materialcab.createCab
|
||||
import com.google.android.gms.cast.framework.CastButtonFactory
|
||||
|
||||
class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridLayoutManager>(),
|
||||
IAlbumClickListener, ICabHolder {
|
||||
|
@ -169,7 +169,7 @@ class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridL
|
|||
setupLayoutMenu(layoutItem.subMenu)
|
||||
setUpSortOrderMenu(menu.findItem(R.id.action_sort_order).subMenu)
|
||||
//Setting up cast button
|
||||
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.action_cast)
|
||||
requireContext().setUpMediaRouteButton(menu)
|
||||
}
|
||||
|
||||
private fun setUpSortOrderMenu(
|
||||
|
|
|
@ -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.R
|
||||
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.fragments.GridStyle
|
||||
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.isActive
|
||||
import com.afollestad.materialcab.createCab
|
||||
import com.google.android.gms.cast.framework.CastButtonFactory
|
||||
|
||||
class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment<ArtistAdapter, GridLayoutManager>(),
|
||||
IArtistClickListener, IAlbumArtistClickListener, ICabHolder {
|
||||
|
@ -180,7 +180,7 @@ class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment<ArtistAdapter, Gri
|
|||
setUpSortOrderMenu(menu.findItem(R.id.action_sort_order).subMenu)
|
||||
setupAlbumArtistMenu(menu)
|
||||
//Setting up cast button
|
||||
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.action_cast)
|
||||
requireContext().setUpMediaRouteButton(menu)
|
||||
}
|
||||
|
||||
private fun setupAlbumArtistMenu(menu: Menu) {
|
||||
|
|
|
@ -82,9 +82,8 @@ abstract class AbsPlayerControlsFragment(@LayoutRes layout: Int) : AbsMusicServi
|
|||
if (seekBar == null) {
|
||||
progressSlider?.valueTo = total.toFloat()
|
||||
|
||||
if (progress > total) return
|
||||
progressSlider?.value = progress.toFloat()
|
||||
|
||||
progressSlider?.value =
|
||||
progress.toFloat().coerceIn(progressSlider?.valueFrom, progressSlider?.valueTo)
|
||||
} else {
|
||||
seekBar?.max = total
|
||||
|
||||
|
|
|
@ -26,12 +26,12 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||
import code.name.monkey.retromusic.EXTRA_GENRE
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.adapter.GenreAdapter
|
||||
import code.name.monkey.retromusic.extensions.setUpMediaRouteButton
|
||||
import code.name.monkey.retromusic.fragments.ReloadType
|
||||
import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewFragment
|
||||
import code.name.monkey.retromusic.interfaces.IGenreClickListener
|
||||
import code.name.monkey.retromusic.model.Genre
|
||||
import code.name.monkey.retromusic.util.RetroUtil
|
||||
import com.google.android.gms.cast.framework.CastButtonFactory
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
|
||||
class
|
||||
|
@ -67,7 +67,7 @@ GenresFragment : AbsRecyclerViewFragment<GenreAdapter, LinearLayoutManager>(),
|
|||
menu.removeItem(R.id.action_sort_order)
|
||||
menu.findItem(R.id.action_settings).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
|
||||
//Setting up cast button
|
||||
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.action_cast)
|
||||
requireContext().setUpMediaRouteButton(menu)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
|
|
@ -35,10 +35,7 @@ import code.name.monkey.retromusic.adapter.HomeAdapter
|
|||
import code.name.monkey.retromusic.databinding.FragmentHomeBinding
|
||||
import code.name.monkey.retromusic.dialogs.CreatePlaylistDialog
|
||||
import code.name.monkey.retromusic.dialogs.ImportPlaylistDialog
|
||||
import code.name.monkey.retromusic.extensions.accentColor
|
||||
import code.name.monkey.retromusic.extensions.dip
|
||||
import code.name.monkey.retromusic.extensions.drawNextToNavbar
|
||||
import code.name.monkey.retromusic.extensions.elevatedAccentColor
|
||||
import code.name.monkey.retromusic.extensions.*
|
||||
import code.name.monkey.retromusic.fragments.ReloadType
|
||||
import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
|
||||
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.util.PreferenceUtil
|
||||
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.transition.MaterialFadeThrough
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
|
@ -212,7 +208,7 @@ class HomeFragment :
|
|||
ATHToolbarActivity.getToolbarBackgroundColor(binding.toolbar)
|
||||
)
|
||||
//Setting up cast button
|
||||
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.action_cast)
|
||||
requireContext().setUpMediaRouteButton(menu)
|
||||
}
|
||||
|
||||
override fun scrollToTop() {
|
||||
|
|
|
@ -30,11 +30,11 @@ import code.name.monkey.retromusic.R
|
|||
import code.name.monkey.retromusic.databinding.FragmentLibraryBinding
|
||||
import code.name.monkey.retromusic.dialogs.CreatePlaylistDialog
|
||||
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.fragments.base.AbsMainActivityFragment
|
||||
import code.name.monkey.retromusic.model.CategoryInfo
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import com.google.android.gms.cast.framework.CastButtonFactory
|
||||
|
||||
class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) {
|
||||
|
||||
|
@ -99,7 +99,7 @@ class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) {
|
|||
getToolbarBackgroundColor(binding.toolbar)
|
||||
)
|
||||
//Setting up cast button
|
||||
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.action_cast)
|
||||
requireContext().setUpMediaRouteButton(menu)
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(item: MenuItem): Boolean {
|
||||
|
|
|
@ -12,11 +12,10 @@
|
|||
* 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.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.MediaStore
|
||||
|
@ -26,23 +25,19 @@ import androidx.activity.result.ActivityResultLauncher
|
|||
import androidx.activity.result.IntentSenderRequest
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.transition.Fade
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import code.name.monkey.appthemehelper.common.ATHToolbarActivity
|
||||
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.activities.MainActivity
|
||||
import code.name.monkey.retromusic.activities.tageditor.TagWriter
|
||||
import code.name.monkey.retromusic.databinding.FragmentLyricsBinding
|
||||
import code.name.monkey.retromusic.databinding.FragmentNormalLyricsBinding
|
||||
import code.name.monkey.retromusic.databinding.FragmentSyncedLyricsBinding
|
||||
import code.name.monkey.retromusic.extensions.*
|
||||
import code.name.monkey.retromusic.extensions.accentColor
|
||||
import code.name.monkey.retromusic.extensions.materialDialog
|
||||
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.AbsMusicServiceFragment
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
|
||||
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.UriUtil
|
||||
import com.afollestad.materialdialogs.input.input
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jaudiotagger.audio.AudioFileIO
|
||||
|
@ -60,14 +54,23 @@ import org.jaudiotagger.tag.FieldKey
|
|||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
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 val binding get() = _binding!!
|
||||
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
|
||||
get() {
|
||||
|
@ -77,22 +80,8 @@ class LyricsFragment : AbsMainActivityFragment(R.layout.fragment_lyrics) {
|
|||
baseUrl += query
|
||||
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 newSyncedLyricsLauncher: ActivityResultLauncher<Intent>
|
||||
private lateinit var editSyncedLyricsLauncher: ActivityResultLauncher<IntentSenderRequest>
|
||||
|
||||
private lateinit var cacheFile: File
|
||||
private var syncedLyrics: String = ""
|
||||
private lateinit var syncedFileUri: Uri
|
||||
private lateinit var updateHelper: MusicProgressViewUpdateHelper
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -103,14 +92,6 @@ class LyricsFragment : AbsMainActivityFragment(R.layout.fragment_lyrics) {
|
|||
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 =
|
||||
registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
|
@ -127,36 +108,42 @@ class LyricsFragment : AbsMainActivityFragment(R.layout.fragment_lyrics) {
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
enterTransition = Fade()
|
||||
exitTransition = Fade()
|
||||
updateTitleSong()
|
||||
lyricsSectionsAdapter = LyricsSectionsAdapter(requireActivity())
|
||||
_binding = FragmentLyricsBinding.bind(view)
|
||||
binding.container.transitionName = "lyrics"
|
||||
updateHelper = MusicProgressViewUpdateHelper(this, 500, 1000)
|
||||
updateTitleSong()
|
||||
setupLyricsView()
|
||||
loadLyrics()
|
||||
|
||||
setupWakelock()
|
||||
setupViews()
|
||||
setupToolbar()
|
||||
}
|
||||
|
||||
private fun setupViews() {
|
||||
binding.lyricsPager.adapter = lyricsSectionsAdapter
|
||||
TabLayoutMediator(binding.tabLyrics, binding.lyricsPager) { tab, position ->
|
||||
tab.text = when (position) {
|
||||
0 -> getString(R.string.synced_lyrics)
|
||||
1 -> getString(R.string.normal_lyrics)
|
||||
else -> ""
|
||||
}
|
||||
}.attach()
|
||||
// lyricsPager.isUserInputEnabled = false
|
||||
private fun setupLyricsView() {
|
||||
binding.lyricsView.apply {
|
||||
setCurrentColor(accentColor())
|
||||
setTimeTextColor(accentColor())
|
||||
setTimelineColor(accentColor())
|
||||
setTimelineTextColor(accentColor())
|
||||
setDraggable(true, LrcView.OnPlayClickListener {
|
||||
MusicPlayerRemote.seekTo(it.toInt())
|
||||
return@OnPlayClickListener true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
binding.tabLyrics.setSelectedTabIndicatorColor(accentColor())
|
||||
binding.tabLyrics.setTabTextColors(textColorSecondary(), accentColor())
|
||||
override fun onUpdateProgressViews(progress: Int, total: Int) {
|
||||
binding.lyricsView.updateTime(progress.toLong())
|
||||
}
|
||||
|
||||
private fun setupViews() {
|
||||
binding.editButton.accentColor()
|
||||
binding.editButton.setOnClickListener {
|
||||
when (binding.lyricsPager.currentItem) {
|
||||
0 -> {
|
||||
when (lyricsType) {
|
||||
LyricsType.SYNCED_LYRICS -> {
|
||||
editSyncedLyrics()
|
||||
}
|
||||
1 -> {
|
||||
LyricsType.NORMAL_LYRICS -> {
|
||||
editNormalLyrics()
|
||||
}
|
||||
}
|
||||
|
@ -166,11 +153,13 @@ class LyricsFragment : AbsMainActivityFragment(R.layout.fragment_lyrics) {
|
|||
override fun onPlayingMetaChanged() {
|
||||
super.onPlayingMetaChanged()
|
||||
updateTitleSong()
|
||||
loadLyrics()
|
||||
}
|
||||
|
||||
override fun onServiceConnected() {
|
||||
super.onServiceConnected()
|
||||
updateTitleSong()
|
||||
loadLyrics()
|
||||
}
|
||||
|
||||
private fun updateTitleSong() {
|
||||
|
@ -190,7 +179,7 @@ class LyricsFragment : AbsMainActivityFragment(R.layout.fragment_lyrics) {
|
|||
}
|
||||
|
||||
override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.menu_search, menu)
|
||||
inflater.inflate(R.menu.menu_lyrics, menu)
|
||||
ToolbarContentTintHelper.handleOnCreateOptionsMenu(
|
||||
requireContext(),
|
||||
binding.toolbar,
|
||||
|
@ -201,27 +190,23 @@ class LyricsFragment : AbsMainActivityFragment(R.layout.fragment_lyrics) {
|
|||
|
||||
override fun onMenuItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == R.id.action_search) {
|
||||
openUrl(when (binding.lyricsPager.currentItem) {
|
||||
0 -> syairSearchLrcUrl
|
||||
1 -> googleSearchLrcUrl
|
||||
else -> googleSearchLrcUrl
|
||||
}
|
||||
)
|
||||
openUrl(googleSearchLrcUrl)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
private fun editNormalLyrics() {
|
||||
var content = ""
|
||||
val file = File(MusicPlayerRemote.currentSong.data)
|
||||
try {
|
||||
content = AudioFileIO.read(file).tagOrCreateDefault.getFirst(FieldKey.LYRICS)
|
||||
private fun editNormalLyrics(lyrics: String? = null) {
|
||||
val file = File(song.data)
|
||||
val content = lyrics ?: try {
|
||||
AudioFileIO.read(file).tagOrCreateDefault.getFirst(FieldKey.LYRICS)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
""
|
||||
}
|
||||
|
||||
val song = song
|
||||
|
||||
materialDialog().show {
|
||||
title(res = R.string.edit_normal_lyrics)
|
||||
input(
|
||||
|
@ -231,7 +216,6 @@ class LyricsFragment : AbsMainActivityFragment(R.layout.fragment_lyrics) {
|
|||
) { _, input ->
|
||||
val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java)
|
||||
fieldKeyValueMap[FieldKey.LYRICS] = input.toString()
|
||||
syncedLyrics = input.toString()
|
||||
GlobalScope.launch {
|
||||
if (VersionUtils.hasR()) {
|
||||
cacheFile = TagWriter.writeTagsToFilesR(
|
||||
|
@ -258,7 +242,7 @@ class LyricsFragment : AbsMainActivityFragment(R.layout.fragment_lyrics) {
|
|||
}
|
||||
}
|
||||
positiveButton(res = R.string.save) {
|
||||
(lyricsSectionsAdapter.fragments[1].first as NormalLyrics).loadNormalLyrics()
|
||||
loadNormalLyrics()
|
||||
}
|
||||
negativeButton(res = android.R.string.cancel)
|
||||
}
|
||||
|
@ -266,9 +250,10 @@ class LyricsFragment : AbsMainActivityFragment(R.layout.fragment_lyrics) {
|
|||
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
private fun editSyncedLyrics() {
|
||||
val content: String = LyricUtil.getStringFromLrc(LyricUtil.getSyncedLyricsFile(song))
|
||||
private fun editSyncedLyrics(lyrics: String? = null) {
|
||||
val content = lyrics ?: LyricUtil.getStringFromLrc(LyricUtil.getSyncedLyricsFile(song))
|
||||
|
||||
val song = song
|
||||
materialDialog().show {
|
||||
title(res = R.string.edit_synced_lyrics)
|
||||
input(
|
||||
|
@ -291,151 +276,97 @@ class LyricsFragment : AbsMainActivityFragment(R.layout.fragment_lyrics) {
|
|||
IntentSenderRequest.Builder(pendingIntent).build()
|
||||
)
|
||||
} else {
|
||||
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
intent.type = "*/*"
|
||||
intent.putExtra(
|
||||
Intent.EXTRA_TITLE,
|
||||
LyricUtil.getLrcOriginalPath(File(song.data).name)
|
||||
)
|
||||
newSyncedLyricsLauncher.launch(intent)
|
||||
val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java)
|
||||
fieldKeyValueMap[FieldKey.LYRICS] = input.toString()
|
||||
GlobalScope.launch {
|
||||
cacheFile = TagWriter.writeTagsToFilesR(
|
||||
requireContext(),
|
||||
AudioTagInfo(listOf(song.data), fieldKeyValueMap, null)
|
||||
)[0]
|
||||
val pendingIntent = MediaStore.createWriteRequest(
|
||||
requireContext().contentResolver,
|
||||
listOf(song.uri)
|
||||
)
|
||||
|
||||
normalLyricsLauncher.launch(
|
||||
IntentSenderRequest.Builder(pendingIntent).build()
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LyricUtil.writeLrc(song, input.toString())
|
||||
}
|
||||
}
|
||||
positiveButton(res = R.string.save) {
|
||||
(lyricsSectionsAdapter.fragments[0].first as SyncedLyrics).loadLRCLyrics()
|
||||
loadLRCLyrics()
|
||||
}
|
||||
negativeButton(res = android.R.string.cancel)
|
||||
}
|
||||
}
|
||||
|
||||
class LyricsSectionsAdapter(fragmentActivity: FragmentActivity) :
|
||||
FragmentStateAdapter(fragmentActivity) {
|
||||
val fragments = listOf(
|
||||
Pair(SyncedLyrics(), R.string.synced_lyrics),
|
||||
Pair(NormalLyrics(), R.string.normal_lyrics)
|
||||
)
|
||||
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return fragments.size
|
||||
private fun loadNormalLyrics() {
|
||||
var lyrics: String? = null
|
||||
val file = File(song.data)
|
||||
try {
|
||||
lyrics = AudioFileIO.read(file).tagOrCreateDefault.getFirst(FieldKey.LYRICS)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
binding.noLyricsFound.isVisible = lyrics.isNullOrEmpty()
|
||||
binding.normalLyrics.text = lyrics
|
||||
}
|
||||
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
return fragments[position].first
|
||||
/**
|
||||
* @return success
|
||||
*/
|
||||
private fun loadLRCLyrics(): Boolean {
|
||||
binding.lyricsView.setLabel(getString(R.string.empty))
|
||||
val lrcFile = LyricUtil.getSyncedLyricsFile(song)
|
||||
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 loadLyrics() {
|
||||
lyricsType = if (!loadLRCLyrics()) {
|
||||
loadNormalLyrics()
|
||||
LyricsType.SYNCED_LYRICS
|
||||
} else {
|
||||
binding.noLyricsFound.isVisible = false
|
||||
binding.normalLyrics.text = ""
|
||||
LyricsType.NORMAL_LYRICS
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
val file = File(MusicPlayerRemote.currentSong.data)
|
||||
try {
|
||||
lyrics = AudioFileIO.read(file).tagOrCreateDefault.getFirst(FieldKey.LYRICS)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
binding.noLyricsFound.isVisible = lyrics.isNullOrEmpty()
|
||||
binding.normalLyrics.text = lyrics
|
||||
}
|
||||
|
||||
override fun onPlayingMetaChanged() {
|
||||
super.onPlayingMetaChanged()
|
||||
loadNormalLyrics()
|
||||
}
|
||||
|
||||
override fun onServiceConnected() {
|
||||
super.onServiceConnected()
|
||||
loadNormalLyrics()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
updateHelper.start()
|
||||
}
|
||||
|
||||
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))
|
||||
LyricUtil.getSyncedLyricsFile(MusicPlayerRemote.currentSong)?.let {
|
||||
binding.lyricsView.loadLrc(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupLyricsView() {
|
||||
binding.lyricsView.apply {
|
||||
setCurrentColor(accentColor())
|
||||
setTimeTextColor(accentColor())
|
||||
setTimelineColor(accentColor())
|
||||
setTimelineTextColor(accentColor())
|
||||
setDraggable(true, LrcView.OnPlayClickListener {
|
||||
MusicPlayerRemote.seekTo(it.toInt())
|
||||
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() {
|
||||
super.onResume()
|
||||
updateHelper.start()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
updateHelper.stop()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
updateHelper.stop()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
if (MusicPlayerRemote.playingQueue.isNotEmpty())
|
||||
(requireActivity() as MainActivity).expandPanel()
|
||||
mainActivity.expandPanel()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
enum class LyricsType {
|
||||
NORMAL_LYRICS,
|
||||
SYNCED_LYRICS
|
||||
}
|
||||
}
|
|
@ -14,7 +14,6 @@
|
|||
*/
|
||||
package code.name.monkey.retromusic.fragments.other
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
|
@ -23,7 +22,6 @@ import android.text.style.ForegroundColorSpan
|
|||
import android.view.GestureDetector
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import androidx.core.text.toSpannable
|
||||
import androidx.core.view.isVisible
|
||||
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) {
|
||||
binding.progressBar.max = total
|
||||
val animator = ObjectAnimator.ofInt(binding.progressBar, "progress", progress)
|
||||
animator.duration = 1000
|
||||
animator.interpolator = DecelerateInterpolator()
|
||||
animator.start()
|
||||
binding.progressBar.progress = progress
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
|
|
@ -11,6 +11,7 @@ import androidx.preference.PreferenceManager
|
|||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.SHOW_LYRICS
|
||||
import code.name.monkey.retromusic.databinding.FragmentCoverLyricsBinding
|
||||
import code.name.monkey.retromusic.extensions.dipToPix
|
||||
import code.name.monkey.retromusic.fragments.NowPlayingScreen
|
||||
import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment
|
||||
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.util.LyricUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import 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?) {
|
||||
if (key == SHOW_LYRICS) {
|
||||
if (sharedPreferences?.getBoolean(key, false) == true) {
|
||||
|
|
|
@ -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.ParallaxPagerTransformer
|
||||
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.color.MediaNotificationProcessor
|
||||
import code.name.monkey.retromusic.util.logD
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
@ -86,21 +85,19 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
|
|||
}
|
||||
|
||||
private fun updateLyrics() {
|
||||
binding.lyricsView.setLabel(context?.getString(R.string.no_lyrics_found))
|
||||
val song = MusicPlayerRemote.currentSong
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val lrcFile = LyricUtil.getSyncedLyricsFile(song)
|
||||
if (lrcFile != null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
binding.lyricsView.loadLrc(lrcFile)
|
||||
}
|
||||
binding.lyricsView.loadLrc(lrcFile)
|
||||
} else {
|
||||
val embeddedLyrics = LyricUtil.getEmbeddedSyncedLyrics(song.data)
|
||||
withContext(Dispatchers.Main) {
|
||||
if (embeddedLyrics != null) {
|
||||
binding.lyricsView.loadLrc(embeddedLyrics)
|
||||
} else {
|
||||
if (embeddedLyrics != null) {
|
||||
binding.lyricsView.loadLrc(embeddedLyrics)
|
||||
} else {
|
||||
withContext(Dispatchers.Main) {
|
||||
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() {
|
||||
logD("Service Connected")
|
||||
updatePlayingQueue()
|
||||
updateLyrics()
|
||||
}
|
||||
|
||||
override fun onPlayingMetaChanged() {
|
||||
logD("Playing Meta Changed")
|
||||
if (viewPager.currentItem != MusicPlayerRemote.position) {
|
||||
viewPager.setCurrentItem(MusicPlayerRemote.position, true)
|
||||
}
|
||||
|
@ -190,7 +185,6 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
|
|||
}
|
||||
|
||||
override fun onQueueChanged() {
|
||||
logD("Queue Changed")
|
||||
updatePlayingQueue()
|
||||
}
|
||||
|
||||
|
@ -224,7 +218,7 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
|
|||
binding.coverLyrics.isVisible = false
|
||||
binding.lyricsView.isVisible = false
|
||||
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()
|
||||
lrcView
|
||||
} else {
|
||||
|
@ -244,7 +238,7 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
|
|||
// Don't show lyrics container for below conditions
|
||||
if (lyricViewNpsList.contains(nps) && PreferenceUtil.showLyrics) {
|
||||
showLyrics(true)
|
||||
if (PreferenceUtil.lyricsType == LyricsType.REPLACE_COVER) {
|
||||
if (PreferenceUtil.lyricsType == CoverLyricsType.REPLACE_COVER) {
|
||||
progressViewUpdateHelper?.start()
|
||||
}
|
||||
} else {
|
||||
|
@ -255,8 +249,7 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
|
|||
|
||||
private fun updatePlayingQueue() {
|
||||
binding.viewPager.apply {
|
||||
adapter = AlbumCoverPagerAdapter(childFragmentManager, MusicPlayerRemote.playingQueue)
|
||||
adapter?.notifyDataSetChanged()
|
||||
adapter = AlbumCoverPagerAdapter(parentFragmentManager, MusicPlayerRemote.playingQueue)
|
||||
setCurrentItem(MusicPlayerRemote.position, true)
|
||||
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 onPageSelected(position: Int) {
|
||||
logD("Page Selected $position")
|
||||
currentPosition = position
|
||||
if (binding.viewPager.adapter != null) {
|
||||
(binding.viewPager.adapter as AlbumCoverPagerAdapter).receiveColor(
|
||||
|
|
|
@ -74,8 +74,8 @@ class CardBlurFragment : AbsPlayerFragment(R.layout.fragment_card_blur_player),
|
|||
libraryViewModel.updateColor(color.backgroundColor)
|
||||
ToolbarContentTintHelper.colorizeToolbar(binding.playerToolbar, Color.WHITE, activity)
|
||||
|
||||
binding.playerToolbar.setTitleTextColor(Color.WHITE)
|
||||
binding.playerToolbar.setSubtitleTextColor(Color.WHITE)
|
||||
binding.title.setTextColor(Color.WHITE)
|
||||
binding.text.setTextColor(Color.WHITE)
|
||||
}
|
||||
|
||||
override fun toggleFavorite(song: Song) {
|
||||
|
@ -94,7 +94,7 @@ class CardBlurFragment : AbsPlayerFragment(R.layout.fragment_card_blur_player),
|
|||
_binding = FragmentCardBlurPlayerBinding.bind(view)
|
||||
setUpSubFragments()
|
||||
setUpPlayerToolbar()
|
||||
binding.cardContainer?.drawAboveSystemBars()
|
||||
binding.playerToolbar.drawAboveSystemBars()
|
||||
}
|
||||
|
||||
private fun setUpSubFragments() {
|
||||
|
@ -130,9 +130,9 @@ class CardBlurFragment : AbsPlayerFragment(R.layout.fragment_card_blur_player),
|
|||
|
||||
private fun updateSong() {
|
||||
val song = MusicPlayerRemote.currentSong
|
||||
binding.playerToolbar.apply {
|
||||
title = song.title
|
||||
subtitle = song.artistName
|
||||
binding.run {
|
||||
title.text = song.title
|
||||
text.text = song.artistName
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,6 @@ import code.name.monkey.retromusic.R
|
|||
import code.name.monkey.retromusic.databinding.FragmentCirclePlayerBinding
|
||||
import code.name.monkey.retromusic.extensions.*
|
||||
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.goToAlbum
|
||||
import code.name.monkey.retromusic.fragments.base.goToArtist
|
||||
|
@ -315,16 +314,10 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
|
|||
val progressSlider = binding.progressSlider
|
||||
progressSlider.valueTo = total.toFloat()
|
||||
|
||||
if (isSeeking) {
|
||||
progressSlider.value = progress.toFloat()
|
||||
} else {
|
||||
progressAnimator =
|
||||
ObjectAnimator.ofFloat(progressSlider, "value", progress.toFloat()).apply {
|
||||
duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME
|
||||
interpolator = LinearInterpolator()
|
||||
start()
|
||||
}
|
||||
}
|
||||
progressSlider.valueTo = total.toFloat()
|
||||
|
||||
progressSlider.value =
|
||||
progress.toFloat().coerceIn(progressSlider.valueFrom, progressSlider.valueTo)
|
||||
|
||||
binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
|
||||
binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
|
||||
|
|
|
@ -28,6 +28,7 @@ import code.name.monkey.retromusic.extensions.show
|
|||
import code.name.monkey.retromusic.extensions.whichFragment
|
||||
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
|
||||
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.glide.GlideApp
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||
|
@ -98,6 +99,7 @@ class FullPlayerFragment : AbsPlayerFragment(R.layout.fragment_full) {
|
|||
controlsFragment.setColor(color)
|
||||
libraryViewModel.updateColor(color.backgroundColor)
|
||||
ToolbarContentTintHelper.colorizeToolbar(binding.playerToolbar, Color.WHITE, activity)
|
||||
binding.coverLyrics.getFragment<CoverLyricsFragment>().setColors(color)
|
||||
}
|
||||
|
||||
override fun onFavoriteToggled() {
|
||||
|
|
|
@ -22,7 +22,6 @@ import android.graphics.PorterDuff
|
|||
import android.graphics.drawable.AnimatedVectorDrawable
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.animation.LinearInterpolator
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
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.extensions.*
|
||||
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.goToAlbum
|
||||
import code.name.monkey.retromusic.fragments.base.goToArtist
|
||||
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.MusicProgressViewUpdateHelper
|
||||
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
|
||||
|
@ -272,6 +271,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
|
|||
updateRepeatState()
|
||||
updateShuffleState()
|
||||
updatePrevNextColor()
|
||||
binding.coverLyrics.getFragment<CoverLyricsFragment>().setColors(color)
|
||||
}
|
||||
|
||||
override fun onFavoriteToggled() {
|
||||
|
@ -381,17 +381,22 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
|
|||
|
||||
private fun setUpPlayPauseFab() {
|
||||
binding.playbackControlsFragment.playPauseButton.setOnClickListener(
|
||||
PlayPauseButtonOnClickHandler())
|
||||
PlayPauseButtonOnClickHandler()
|
||||
)
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private fun setUpPrevNext() {
|
||||
updatePrevNextColor()
|
||||
binding.playbackControlsFragment.nextButton.setOnTouchListener(MusicSeekSkipTouchListener(
|
||||
requireActivity(),
|
||||
true))
|
||||
binding.playbackControlsFragment.nextButton.setOnTouchListener(
|
||||
MusicSeekSkipTouchListener(
|
||||
requireActivity(),
|
||||
true
|
||||
)
|
||||
)
|
||||
binding.playbackControlsFragment.previousButton.setOnTouchListener(
|
||||
MusicSeekSkipTouchListener(requireActivity(), false))
|
||||
MusicSeekSkipTouchListener(requireActivity(), false)
|
||||
)
|
||||
}
|
||||
|
||||
private fun updatePrevNextColor() {
|
||||
|
@ -573,16 +578,9 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
|
|||
val progressSlider = binding.playbackControlsFragment.progressSlider
|
||||
progressSlider.valueTo = total.toFloat()
|
||||
|
||||
if (isSeeking) {
|
||||
progressSlider.value = progress.toFloat()
|
||||
} else {
|
||||
progressAnimator =
|
||||
ObjectAnimator.ofFloat(progressSlider, "value", progress.toFloat()).apply {
|
||||
duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME
|
||||
interpolator = LinearInterpolator()
|
||||
start()
|
||||
}
|
||||
}
|
||||
progressSlider.value =
|
||||
progress.toFloat().coerceIn(progressSlider.valueFrom, progressSlider.valueTo)
|
||||
|
||||
binding.playbackControlsFragment.songTotalTime.text =
|
||||
MusicUtil.getReadableDurationString(total.toLong())
|
||||
binding.playbackControlsFragment.songCurrentProgress.text =
|
||||
|
|
|
@ -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.ICabHolder
|
||||
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.ThemedFastScroller
|
||||
import com.afollestad.materialcab.attached.AttachedCab
|
||||
import com.afollestad.materialcab.attached.destroy
|
||||
import com.afollestad.materialcab.attached.isActive
|
||||
|
@ -71,6 +73,8 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
|
|||
binding.container.transitionName = "playlist"
|
||||
playlist = arguments.extraPlaylist
|
||||
binding.toolbar.title = playlist.playlistEntity.playlistName
|
||||
binding.toolbar.subtitle =
|
||||
MusicUtil.getPlaylistInfoString(requireContext(), playlist.songs.toSongs())
|
||||
setUpRecyclerView()
|
||||
viewModel.getSongs().observe(viewLifecycleOwner) {
|
||||
songs(it.toSongs())
|
||||
|
@ -109,6 +113,7 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
|
|||
binding.recyclerView.apply {
|
||||
layoutManager = LinearLayoutManager(requireContext())
|
||||
binding.recyclerView.adapter = wrappedAdapter
|
||||
ThemedFastScroller.create(this)
|
||||
}
|
||||
playlistSongAdapter.registerAdapterDataObserver(object :
|
||||
RecyclerView.AdapterDataObserver() {
|
||||
|
|
|
@ -24,13 +24,13 @@ import code.name.monkey.retromusic.EXTRA_PLAYLIST
|
|||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.adapter.playlist.PlaylistAdapter
|
||||
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.base.AbsRecyclerViewCustomGridSizeFragment
|
||||
import code.name.monkey.retromusic.helper.SortOrder.PlaylistSortOrder
|
||||
import code.name.monkey.retromusic.interfaces.IPlaylistClickListener
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.RetroUtil
|
||||
import com.google.android.gms.cast.framework.CastButtonFactory
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
|
||||
class PlaylistsFragment :
|
||||
|
@ -85,7 +85,7 @@ class PlaylistsFragment :
|
|||
setUpSortOrderMenu(menu.findItem(R.id.action_sort_order).subMenu)
|
||||
MenuCompat.setGroupDividerEnabled(menu, true)
|
||||
//Setting up cast button
|
||||
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.action_cast)
|
||||
requireContext().setUpMediaRouteButton(menu)
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(item: MenuItem): Boolean {
|
||||
|
|
|
@ -26,6 +26,7 @@ import androidx.preference.PreferenceManager
|
|||
import code.name.monkey.appthemehelper.common.prefs.supportv7.ATEPreferenceFragmentCompat
|
||||
import code.name.monkey.retromusic.R
|
||||
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.preferences.*
|
||||
import code.name.monkey.retromusic.util.NavigationUtil
|
||||
|
@ -39,7 +40,7 @@ abstract class AbsSettingsFragment : ATEPreferenceFragmentCompat() {
|
|||
|
||||
internal fun showProToastAndNavigate(message: String) {
|
||||
showToast(getString(R.string.message_pro_feature, message))
|
||||
NavigationUtil.goToProVersion(requireActivity())
|
||||
requireContext().goToProVersion()
|
||||
}
|
||||
|
||||
internal fun setSummary(preference: Preference, value: Any?) {
|
||||
|
|
|
@ -27,6 +27,7 @@ import code.name.monkey.retromusic.App
|
|||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.databinding.FragmentMainSettingsBinding
|
||||
import code.name.monkey.retromusic.extensions.drawAboveSystemBarsWithPadding
|
||||
import code.name.monkey.retromusic.extensions.goToProVersion
|
||||
import code.name.monkey.retromusic.util.NavigationUtil
|
||||
|
||||
class MainSettingsFragment : Fragment(), View.OnClickListener {
|
||||
|
@ -77,11 +78,11 @@ class MainSettingsFragment : Fragment(), View.OnClickListener {
|
|||
binding.buyProContainer.apply {
|
||||
isGone = App.isProVersion()
|
||||
setOnClickListener {
|
||||
NavigationUtil.goToProVersion(requireContext())
|
||||
requireContext().goToProVersion()
|
||||
}
|
||||
}
|
||||
binding.buyPremium.setOnClickListener {
|
||||
NavigationUtil.goToProVersion(requireContext())
|
||||
requireContext().goToProVersion()
|
||||
}
|
||||
ThemeStore.accentColor(requireContext()).let {
|
||||
binding.buyPremium.setTextColor(it)
|
||||
|
|
|
@ -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.LAST_ADDED_CUTOFF
|
||||
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.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 java.util.*
|
||||
|
||||
/**
|
||||
* @author Hemanth S (h4h13).
|
||||
|
@ -58,21 +56,7 @@ class OtherSettingsFragment : AbsSettingsFragment() {
|
|||
val languagePreference: Preference? = findPreference(LANGUAGE_NAME)
|
||||
languagePreference?.setOnPreferenceChangeListener { prefs, newValue ->
|
||||
setSummary(prefs, newValue)
|
||||
val code = 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()
|
||||
}
|
||||
requireActivity().installLanguageAndRecreate(newValue.toString())
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import androidx.annotation.LayoutRes
|
|||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import code.name.monkey.retromusic.R
|
||||
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.fragments.GridStyle
|
||||
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.isActive
|
||||
import com.afollestad.materialcab.createCab
|
||||
import com.google.android.gms.cast.framework.CastButtonFactory
|
||||
|
||||
class SongsFragment : AbsRecyclerViewCustomGridSizeFragment<SongAdapter, GridLayoutManager>(),
|
||||
ICabHolder {
|
||||
|
@ -136,7 +136,7 @@ class SongsFragment : AbsRecyclerViewCustomGridSizeFragment<SongAdapter, GridLay
|
|||
setupLayoutMenu(layoutItem.subMenu)
|
||||
setUpSortOrderMenu(menu.findItem(R.id.action_sort_order).subMenu)
|
||||
//Setting up cast button
|
||||
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.action_cast)
|
||||
requireContext().setUpMediaRouteButton(menu)
|
||||
}
|
||||
|
||||
private fun setUpSortOrderMenu(sortOrderMenu: SubMenu) {
|
||||
|
|
|
@ -27,10 +27,10 @@ import code.name.monkey.retromusic.R
|
|||
import code.name.monkey.retromusic.extensions.showToast
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
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.util.getExternalStorageDirectory
|
||||
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.inject
|
||||
import java.io.File
|
||||
|
@ -171,9 +171,6 @@ object MusicPlayerRemote : KoinComponent {
|
|||
return musicService?.playingQueue?.size ?: -1
|
||||
}
|
||||
|
||||
/**
|
||||
* Async
|
||||
*/
|
||||
fun playSongAt(position: Int) {
|
||||
musicService?.playSongAt(position)
|
||||
}
|
||||
|
@ -456,8 +453,8 @@ object MusicPlayerRemote : KoinComponent {
|
|||
.dropLastWhile { it.isEmpty() }.toTypedArray()[1]
|
||||
}
|
||||
|
||||
fun switchToRemotePlayback(castSession: CastSession) {
|
||||
musicService?.switchToRemotePlayback(castSession)
|
||||
fun switchToRemotePlayback(castPlayer: CastPlayer) {
|
||||
musicService?.switchToRemotePlayback(castPlayer)
|
||||
}
|
||||
|
||||
fun switchToLocalPlayback() {
|
||||
|
|
|
@ -24,9 +24,10 @@ class MusicProgressViewUpdateHelper : Handler {
|
|||
private var callback: Callback? = null
|
||||
private var intervalPlaying: Int = 0
|
||||
private var intervalPaused: Int = 0
|
||||
private var firstUpdate = true
|
||||
|
||||
fun start() {
|
||||
queueNextRefresh(1)
|
||||
queueNextRefresh(refreshProgressViews().toLong())
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
|
@ -59,10 +60,11 @@ class MusicProgressViewUpdateHelper : Handler {
|
|||
private fun refreshProgressViews(): Int {
|
||||
val progressMillis = MusicPlayerRemote.songProgressMillis
|
||||
val totalMillis = MusicPlayerRemote.songDurationMillis
|
||||
if (totalMillis > 0)
|
||||
if (totalMillis > 0) {
|
||||
firstUpdate = false
|
||||
callback?.onUpdateProgressViews(progressMillis, totalMillis)
|
||||
|
||||
if (!MusicPlayerRemote.isPlaying) {
|
||||
}
|
||||
if (!MusicPlayerRemote.isPlaying && !firstUpdate) {
|
||||
return intervalPaused
|
||||
}
|
||||
|
||||
|
@ -84,7 +86,7 @@ class MusicProgressViewUpdateHelper : Handler {
|
|||
companion object {
|
||||
private const val CMD_REFRESH_PROGRESS_VIEWS = 1
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ import android.content.Context
|
|||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.AsyncTask
|
||||
import android.os.Looper
|
||||
import android.text.Layout
|
||||
import android.text.StaticLayout
|
||||
|
@ -35,14 +34,15 @@ import android.widget.Scroller
|
|||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.withSave
|
||||
import code.name.monkey.retromusic.R
|
||||
import kotlinx.coroutines.*
|
||||
import java.io.File
|
||||
import java.lang.Runnable
|
||||
import kotlin.math.abs
|
||||
|
||||
/**
|
||||
* 歌词 Created by wcy on 2015/11/9.
|
||||
*/
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
@Suppress("deprecation")
|
||||
class CoverLrcView @JvmOverloads constructor(
|
||||
context: Context?,
|
||||
attrs: AttributeSet? = null,
|
||||
|
@ -72,7 +72,6 @@ class CoverLrcView @JvmOverloads constructor(
|
|||
private var mScroller: Scroller? = null
|
||||
private var mOffset = 0f
|
||||
private var mCurrentLine = 0
|
||||
private var flag: Any? = null
|
||||
private var isShowTimeline = false
|
||||
private var isTouching = false
|
||||
private var isFling = false
|
||||
|
@ -85,9 +84,8 @@ class CoverLrcView @JvmOverloads constructor(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手势监听器
|
||||
*/
|
||||
private val viewScope = CoroutineScope(Dispatchers.Main + Job())
|
||||
|
||||
private val mSimpleOnGestureListener: SimpleOnGestureListener =
|
||||
object : SimpleOnGestureListener() {
|
||||
override fun onDown(e: MotionEvent): Boolean {
|
||||
|
@ -251,42 +249,31 @@ class CoverLrcView @JvmOverloads constructor(
|
|||
mScroller = Scroller(context)
|
||||
}
|
||||
|
||||
/** 设置非当前行歌词字体颜色 */
|
||||
fun setNormalColor(normalColor: Int) {
|
||||
mNormalTextColor = normalColor
|
||||
postInvalidate()
|
||||
}
|
||||
|
||||
/** 设置当前行歌词的字体颜色 */
|
||||
fun setCurrentColor(currentColor: Int) {
|
||||
mCurrentTextColor = currentColor
|
||||
postInvalidate()
|
||||
}
|
||||
|
||||
/** 设置拖动歌词时选中歌词的字体颜色 */
|
||||
fun setTimelineTextColor(timelineTextColor: Int) {
|
||||
mTimelineTextColor = timelineTextColor
|
||||
postInvalidate()
|
||||
}
|
||||
|
||||
/** 设置拖动歌词时时间线的颜色 */
|
||||
fun setTimelineColor(timelineColor: Int) {
|
||||
mTimelineColor = timelineColor
|
||||
postInvalidate()
|
||||
}
|
||||
|
||||
/** 设置拖动歌词时右侧时间字体颜色 */
|
||||
fun setTimeTextColor(timeTextColor: Int) {
|
||||
mTimeTextColor = timeTextColor
|
||||
postInvalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置歌词是否允许拖动
|
||||
*
|
||||
* @param draggable 是否允许拖动
|
||||
* @param onPlayClickListener 设置歌词拖动后播放按钮点击监听器,如果允许拖动,则不能为 null
|
||||
*/
|
||||
fun setDraggable(draggable: Boolean, onPlayClickListener: OnPlayClickListener?) {
|
||||
mOnPlayClickListener = if (draggable) {
|
||||
requireNotNull(onPlayClickListener) { "if draggable == true, onPlayClickListener must not be null" }
|
||||
|
@ -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?) {
|
||||
runOnUi {
|
||||
mDefaultLabel = label
|
||||
|
@ -314,106 +290,40 @@ class CoverLrcView @JvmOverloads constructor(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载歌词文件
|
||||
*
|
||||
* @param lrcFile 歌词文件
|
||||
*/
|
||||
fun loadLrc(lrcFile: File) {
|
||||
loadLrc(lrcFile, null)
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载双语歌词文件,两种语言的歌词时间戳需要一致
|
||||
*
|
||||
* @param mainLrcFile 第一种语言歌词文件
|
||||
* @param secondLrcFile 第二种语言歌词文件
|
||||
*/
|
||||
private fun loadLrc(mainLrcFile: File, secondLrcFile: File?) {
|
||||
runOnUi {
|
||||
reset()
|
||||
val sb = StringBuilder("file://")
|
||||
sb.append(mainLrcFile.path)
|
||||
if (secondLrcFile != null) {
|
||||
sb.append("#").append(secondLrcFile.path)
|
||||
viewScope.launch(Dispatchers.IO) {
|
||||
val lrcEntries = LrcUtils.parseLrc(arrayOf(lrcFile, null))
|
||||
withContext(Dispatchers.Main) {
|
||||
onLrcLoaded(lrcEntries)
|
||||
}
|
||||
}
|
||||
val flag = sb.toString()
|
||||
this.flag = flag
|
||||
object : AsyncTask<File?, Int?, List<LrcEntry>>() {
|
||||
override fun doInBackground(vararg params: File?): List<LrcEntry>? {
|
||||
return LrcUtils.parseLrc(params)
|
||||
}
|
||||
|
||||
override fun onPostExecute(lrcEntries: List<LrcEntry>) {
|
||||
if (flag == flag) {
|
||||
onLrcLoaded(lrcEntries)
|
||||
this@CoverLrcView.flag = null
|
||||
}
|
||||
}
|
||||
}.execute(mainLrcFile, secondLrcFile)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载歌词文本
|
||||
*
|
||||
* @param lrcText 歌词文本
|
||||
*/
|
||||
fun loadLrc(lrcText: String?) {
|
||||
loadLrc(lrcText, null)
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载双语歌词文本,两种语言的歌词时间戳需要一致
|
||||
*
|
||||
* @param mainLrcText 第一种语言歌词文本
|
||||
* @param secondLrcText 第二种语言歌词文本
|
||||
*/
|
||||
private fun loadLrc(mainLrcText: String?, secondLrcText: String?) {
|
||||
runOnUi {
|
||||
reset()
|
||||
val sb = StringBuilder("file://")
|
||||
sb.append(mainLrcText)
|
||||
if (secondLrcText != null) {
|
||||
sb.append("#").append(secondLrcText)
|
||||
viewScope.launch(Dispatchers.IO) {
|
||||
val lrcEntries = LrcUtils.parseLrc(arrayOf(lrcText, null))
|
||||
withContext(Dispatchers.Main) {
|
||||
onLrcLoaded(lrcEntries)
|
||||
}
|
||||
}
|
||||
val flag = sb.toString()
|
||||
this.flag = flag
|
||||
object : AsyncTask<String?, Int?, List<LrcEntry>>() {
|
||||
override fun doInBackground(vararg params: String?): List<LrcEntry>? {
|
||||
return LrcUtils.parseLrc(params)
|
||||
}
|
||||
|
||||
override fun onPostExecute(lrcEntries: List<LrcEntry>) {
|
||||
if (flag == flag) {
|
||||
onLrcLoaded(lrcEntries)
|
||||
this@CoverLrcView.flag = null
|
||||
}
|
||||
}
|
||||
}.execute(mainLrcText, secondLrcText)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 歌词是否有效
|
||||
*
|
||||
* @return true,如果歌词有效,否则false
|
||||
*/
|
||||
fun hasLrc(): Boolean {
|
||||
return mLrcEntryList.isNotEmpty()
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新歌词
|
||||
*
|
||||
* @param time 当前播放时间
|
||||
*/
|
||||
fun updateTime(time: Long) {
|
||||
runOnUi {
|
||||
if (!hasLrc()) {
|
||||
return@runOnUi
|
||||
}
|
||||
val line = findShowLine(time)
|
||||
val line = findShowLine(time + 300L)
|
||||
if (line != mCurrentLine) {
|
||||
mCurrentLine = line
|
||||
if (!isShowTimeline) {
|
||||
|
@ -441,9 +351,9 @@ class CoverLrcView @JvmOverloads constructor(
|
|||
super.onDraw(canvas)
|
||||
val centerY = height / 2
|
||||
|
||||
// 无歌词文件
|
||||
if (!hasLrc()) {
|
||||
mLrcPaint.color = mCurrentTextColor
|
||||
@Suppress("Deprecation")
|
||||
@SuppressLint("DrawAllocation") val staticLayout = StaticLayout(
|
||||
mDefaultLabel,
|
||||
mLrcPaint,
|
||||
|
@ -485,11 +395,6 @@ class CoverLrcView @JvmOverloads constructor(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 画一行歌词
|
||||
*
|
||||
* @param y 歌词中心 Y 坐标
|
||||
*/
|
||||
private fun drawText(canvas: Canvas, staticLayout: StaticLayout, y: Float) {
|
||||
canvas.withSave {
|
||||
translate(mLrcPadding, y - (staticLayout.height shr 1))
|
||||
|
@ -539,6 +444,7 @@ class CoverLrcView @JvmOverloads constructor(
|
|||
|
||||
override fun onDetachedFromWindow() {
|
||||
removeCallbacks(hideTimelineRunnable)
|
||||
viewScope.cancel()
|
||||
super.onDetachedFromWindow()
|
||||
}
|
||||
|
||||
|
@ -582,12 +488,10 @@ class CoverLrcView @JvmOverloads constructor(
|
|||
invalidate()
|
||||
}
|
||||
|
||||
/** 将中心行微调至正中心 */
|
||||
private fun adjustCenter() {
|
||||
smoothScrollTo(centerLine, ADJUST_DURATION)
|
||||
}
|
||||
|
||||
/** 滚动到某一行 */
|
||||
private fun smoothScrollTo(line: Int, duration: Long = mAnimationDuration) {
|
||||
val offset = getOffset(line)
|
||||
endAnimation()
|
||||
|
@ -602,14 +506,12 @@ class CoverLrcView @JvmOverloads constructor(
|
|||
}
|
||||
}
|
||||
|
||||
/** 结束滚动动画 */
|
||||
private fun endAnimation() {
|
||||
if (mAnimator != null && mAnimator!!.isRunning) {
|
||||
mAnimator!!.end()
|
||||
}
|
||||
}
|
||||
|
||||
/** 二分法查找当前时间应该显示的行数(最后一个 <= time 的行数) */
|
||||
private fun findShowLine(time: Long): Int {
|
||||
var left = 0
|
||||
var right = mLrcEntryList.size
|
||||
|
@ -628,7 +530,6 @@ class CoverLrcView @JvmOverloads constructor(
|
|||
return 0
|
||||
}
|
||||
|
||||
/** 获取当前在视图中央的行数 */
|
||||
private val centerLine: Int
|
||||
get() {
|
||||
var centerLine = 0
|
||||
|
@ -642,7 +543,6 @@ class CoverLrcView @JvmOverloads constructor(
|
|||
return centerLine
|
||||
}
|
||||
|
||||
/** 获取歌词距离视图顶部的距离 采用懒加载方式 */
|
||||
private fun getOffset(line: Int): Float {
|
||||
if (mLrcEntryList.isEmpty()) return 0F
|
||||
if (mLrcEntryList[line].offset == Float.MIN_VALUE) {
|
||||
|
@ -656,11 +556,9 @@ class CoverLrcView @JvmOverloads constructor(
|
|||
return mLrcEntryList[line].offset
|
||||
}
|
||||
|
||||
/** 获取歌词宽度 */
|
||||
private val lrcWidth: Float
|
||||
get() = width - mLrcPadding * 2
|
||||
|
||||
/** 在主线程中运行 */
|
||||
private fun runOnUi(r: Runnable) {
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
r.run()
|
||||
|
@ -669,13 +567,7 @@ class CoverLrcView @JvmOverloads constructor(
|
|||
}
|
||||
}
|
||||
|
||||
/** 播放按钮点击监听器,点击后应该跳转到指定播放位置 */
|
||||
fun interface OnPlayClickListener {
|
||||
/**
|
||||
* 播放按钮被点击,应该跳转到指定播放位置
|
||||
*
|
||||
* @return 是否成功消费该事件,如果成功消费,则会更新UI
|
||||
*/
|
||||
fun onPlayClick(time: Long): Boolean
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -81,7 +81,7 @@ class AlbumCoverStylePreferenceDialog : DialogFragment(),
|
|||
if (isAlbumCoverStyle(coverStyle)) {
|
||||
val result = getString(coverStyle.titleRes) + " theme is Pro version feature."
|
||||
showToast(result)
|
||||
NavigationUtil.goToProVersion(requireActivity())
|
||||
requireContext().goToProVersion()
|
||||
} else {
|
||||
PreferenceUtil.albumCoverStyle = coverStyle
|
||||
}
|
||||
|
|
|
@ -91,7 +91,7 @@ class NowPlayingScreenPreferenceDialog : DialogFragment(), ViewPager.OnPageChang
|
|||
val result =
|
||||
"${getString(nowPlayingScreen.titleRes)} theme is Pro version feature."
|
||||
showToast(result)
|
||||
NavigationUtil.goToProVersion(requireContext())
|
||||
requireContext().goToProVersion()
|
||||
} else {
|
||||
PreferenceUtil.nowPlayingScreen = nowPlayingScreen
|
||||
}
|
||||
|
|
|
@ -3,8 +3,6 @@ package code.name.monkey.retromusic.service
|
|||
import android.animation.Animator
|
||||
import android.animation.ValueAnimator
|
||||
import android.media.MediaPlayer
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.core.animation.doOnEnd
|
||||
import code.name.monkey.retromusic.service.playback.Playback
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
|
@ -12,16 +10,16 @@ import code.name.monkey.retromusic.util.PreferenceUtil
|
|||
class AudioFader {
|
||||
companion object {
|
||||
|
||||
inline fun createFadeAnimator(
|
||||
fun createFadeAnimator(
|
||||
fadeInMp: 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? {
|
||||
val duration = PreferenceUtil.crossFadeDuration * 1000
|
||||
if (duration == 0) {
|
||||
return null
|
||||
}
|
||||
return ValueAnimator.ofFloat(1f, 0f).apply {
|
||||
return ValueAnimator.ofFloat(0f, 1f).apply {
|
||||
this.duration = duration.toLong()
|
||||
addUpdateListener { animation: ValueAnimator ->
|
||||
fadeInMp.setVolume(
|
||||
|
@ -36,15 +34,14 @@ class AudioFader {
|
|||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun startFadeAnimator(
|
||||
playback: Playback,
|
||||
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()
|
||||
if (duration == 0L) {
|
||||
callback.run()
|
||||
callback?.run()
|
||||
return
|
||||
}
|
||||
val startValue = if (fadeIn) 0f else 1.0f
|
||||
|
@ -52,16 +49,12 @@ class AudioFader {
|
|||
val animator = ValueAnimator.ofFloat(startValue, endValue)
|
||||
animator.duration = duration
|
||||
animator.addUpdateListener { animation: ValueAnimator ->
|
||||
playback.setVolume(
|
||||
animation.animatedValue as Float
|
||||
)
|
||||
playback.setVolume(animation.animatedValue as Float)
|
||||
}
|
||||
animator.doOnEnd {
|
||||
callback.run()
|
||||
}
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
animator.start()
|
||||
callback?.run()
|
||||
}
|
||||
animator.start()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -37,7 +37,7 @@ class CrossFadePlayer(context: Context) : LocalPlayback(context) {
|
|||
private var crossFadeAnimator: Animator? = null
|
||||
override var callbacks: PlaybackCallbacks? = null
|
||||
private var crossFadeDuration = PreferenceUtil.crossFadeDuration
|
||||
private var isCrossFading = false
|
||||
var isCrossFading = false
|
||||
|
||||
init {
|
||||
player1.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK)
|
||||
|
@ -120,17 +120,25 @@ class CrossFadePlayer(context: Context) : LocalPlayback(context) {
|
|||
override val isPlaying: Boolean
|
||||
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
|
||||
mIsInitialized = false
|
||||
/* We've already set DataSource if initialized is true in setNextDataSource */
|
||||
return if (!hasDataSource) {
|
||||
mIsInitialized = setDataSourceImpl(getCurrentPlayer()!!, song.uri.toString())
|
||||
if (!hasDataSource) {
|
||||
getCurrentPlayer()?.let {
|
||||
setDataSourceImpl(it, song.uri.toString()) { success ->
|
||||
mIsInitialized = success
|
||||
completion(success)
|
||||
}
|
||||
}
|
||||
hasDataSource = true
|
||||
mIsInitialized
|
||||
} else {
|
||||
completion(true)
|
||||
mIsInitialized = true
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -285,8 +293,8 @@ class CrossFadePlayer(context: Context) : LocalPlayback(context) {
|
|||
val nextSong = MusicPlayerRemote.nextSong
|
||||
// Switch to other player (Crossfade) only if next song exists
|
||||
if (nextSong != null) {
|
||||
if (setDataSourceImpl(player, nextSong.uri.toString())) {
|
||||
switchPlayer()
|
||||
setDataSourceImpl(player, nextSong.uri.toString()) { success ->
|
||||
if (success) switchPlayer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -295,7 +303,7 @@ class CrossFadePlayer(context: Context) : LocalPlayback(context) {
|
|||
|
||||
private fun switchPlayer() {
|
||||
getNextPlayer()?.start()
|
||||
crossFade(getCurrentPlayer()!!, getNextPlayer()!!)
|
||||
crossFade(getNextPlayer()!!, getCurrentPlayer()!!)
|
||||
currentPlayer =
|
||||
if (currentPlayer == CurrentPlayer.PLAYER_ONE || currentPlayer == CurrentPlayer.NOT_SET) {
|
||||
CurrentPlayer.PLAYER_TWO
|
||||
|
@ -325,6 +333,10 @@ internal fun crossFadeScope(): CoroutineScope = CoroutineScope(Job() + Dispatche
|
|||
|
||||
fun MediaPlayer.setPlaybackSpeedPitch(speed: Float, pitch: Float) {
|
||||
if (hasMarshmallow()) {
|
||||
val wasPlaying = isPlaying
|
||||
playbackParams = PlaybackParams().setSpeed(speed).setPitch(pitch)
|
||||
if (!wasPlaying) {
|
||||
pause()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
* @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(
|
||||
player: MediaPlayer,
|
||||
path: String,
|
||||
completion: (success: Boolean) -> Unit,
|
||||
) {
|
||||
player.reset()
|
||||
try {
|
||||
player.reset()
|
||||
player.setOnPreparedListener(null)
|
||||
if (path.startsWith("content://")) {
|
||||
player.setDataSource(context, path.toUri())
|
||||
} else {
|
||||
|
@ -123,14 +126,17 @@ abstract class LocalPlayback(val context: Context) : Playback, MediaPlayer.OnErr
|
|||
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
||||
.build()
|
||||
)
|
||||
player.prepare()
|
||||
player.setOnPreparedListener {
|
||||
player.setOnPreparedListener(null)
|
||||
completion(true)
|
||||
}
|
||||
player.prepareAsync()
|
||||
} catch (e: Exception) {
|
||||
completion(false)
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
player.setOnCompletionListener(this)
|
||||
player.setOnErrorListener(this)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun unregisterBecomingNoisyReceiver() {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -46,13 +46,19 @@ class MultiPlayer(context: Context) : LocalPlayback(context) {
|
|||
* @param song The song object you want to play
|
||||
* @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 = setDataSourceImpl(mCurrentMediaPlayer, song.uri.toString())
|
||||
if (isInitialized) {
|
||||
setNextDataSource(null)
|
||||
setDataSourceImpl(mCurrentMediaPlayer, song.uri.toString()) { success ->
|
||||
isInitialized = success
|
||||
if (isInitialized) {
|
||||
setNextDataSource(null)
|
||||
}
|
||||
completion(isInitialized)
|
||||
}
|
||||
return isInitialized
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -80,26 +86,28 @@ class MultiPlayer(context: Context) : LocalPlayback(context) {
|
|||
mNextMediaPlayer = MediaPlayer()
|
||||
mNextMediaPlayer?.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK)
|
||||
mNextMediaPlayer?.audioSessionId = audioSessionId
|
||||
if (setDataSourceImpl(mNextMediaPlayer!!, path)) {
|
||||
try {
|
||||
mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Log.e(TAG, "setNextDataSource: setNextMediaPlayer()", e)
|
||||
setDataSourceImpl(mNextMediaPlayer!!, path) { success ->
|
||||
if (success) {
|
||||
try {
|
||||
mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Log.e(TAG, "setNextDataSource: setNextMediaPlayer()", e)
|
||||
if (mNextMediaPlayer != null) {
|
||||
mNextMediaPlayer?.release()
|
||||
mNextMediaPlayer = null
|
||||
}
|
||||
} catch (e: IllegalStateException) {
|
||||
Log.e(TAG, "setNextDataSource: setNextMediaPlayer()", e)
|
||||
if (mNextMediaPlayer != null) {
|
||||
mNextMediaPlayer?.release()
|
||||
mNextMediaPlayer = null
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (mNextMediaPlayer != null) {
|
||||
mNextMediaPlayer?.release()
|
||||
mNextMediaPlayer = null
|
||||
}
|
||||
} catch (e: IllegalStateException) {
|
||||
Log.e(TAG, "setNextDataSource: setNextMediaPlayer()", e)
|
||||
if (mNextMediaPlayer != null) {
|
||||
mNextMediaPlayer?.release()
|
||||
mNextMediaPlayer = null
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (mNextMediaPlayer != null) {
|
||||
mNextMediaPlayer?.release()
|
||||
mNextMediaPlayer = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ package code.name.monkey.retromusic.service
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.BluetoothDevice.EXTRA_DEVICE
|
||||
|
@ -39,7 +40,6 @@ import android.widget.Toast
|
|||
import androidx.core.content.edit
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.media.MediaBrowserServiceCompat
|
||||
import androidx.media.session.MediaButtonReceiver.handleIntent
|
||||
import androidx.preference.PreferenceManager
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
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.Target
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import com.google.android.gms.cast.framework.CastSession
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.Dispatchers.Default
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
|
@ -94,6 +93,7 @@ import kotlinx.coroutines.Dispatchers.Main
|
|||
import org.koin.java.KoinJavaComponent.get
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
* @author Karim Abou Zeid (kabouzeid), Andrew Neal. Modified by Prathamesh More
|
||||
*/
|
||||
|
@ -117,11 +117,6 @@ class MusicService : MediaBrowserServiceCompat(),
|
|||
private var trackEndedByCrossfade = false
|
||||
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
|
||||
var position = -1
|
||||
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
|
||||
|
||||
var repeatMode = 0
|
||||
|
@ -282,12 +276,6 @@ class MusicService : MediaBrowserServiceCompat(),
|
|||
playbackManager.setCallbacks(this)
|
||||
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())
|
||||
registerReceiver(widgetIntentReceiver, IntentFilter(APP_WIDGET_UPDATE))
|
||||
registerReceiver(updateFavoriteReceiver, IntentFilter(FAVORITE_STATE_CHANGED))
|
||||
|
@ -296,13 +284,17 @@ class MusicService : MediaBrowserServiceCompat(),
|
|||
notificationManager = getSystemService()
|
||||
initNotification()
|
||||
mediaStoreObserver = MediaStoreObserver(this, playerHandler!!)
|
||||
throttledSeekHandler = ThrottledSeekHandler(this, playerHandler!!)
|
||||
contentResolver.registerContentObserver(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
|
||||
throttledSeekHandler = ThrottledSeekHandler(this, Handler(mainLooper))
|
||||
contentResolver.registerContentObserver(
|
||||
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
|
||||
true,
|
||||
mediaStoreObserver)
|
||||
contentResolver.registerContentObserver(MediaStore.Audio.Media.INTERNAL_CONTENT_URI,
|
||||
mediaStoreObserver
|
||||
)
|
||||
contentResolver.registerContentObserver(
|
||||
MediaStore.Audio.Media.INTERNAL_CONTENT_URI,
|
||||
true,
|
||||
mediaStoreObserver)
|
||||
mediaStoreObserver
|
||||
)
|
||||
val audioVolumeObserver = AudioVolumeObserver(this)
|
||||
audioVolumeObserver.register(AudioManager.STREAM_MUSIC, this)
|
||||
registerOnSharedPreferenceChangedListener(this)
|
||||
|
@ -330,6 +322,7 @@ class MusicService : MediaBrowserServiceCompat(),
|
|||
mediaSession?.isActive = false
|
||||
quit()
|
||||
releaseResources()
|
||||
serviceScope.cancel()
|
||||
contentResolver.unregisterContentObserver(mediaStoreObserver)
|
||||
unregisterOnSharedPreferenceChangedListener(this)
|
||||
wakeLock?.release()
|
||||
|
@ -441,8 +434,11 @@ class MusicService : MediaBrowserServiceCompat(),
|
|||
}
|
||||
|
||||
private fun setPosition(position: Int) {
|
||||
openTrackAndPrepareNextAt(position)
|
||||
notifyChange(PLAY_STATE_CHANGED)
|
||||
openTrackAndPrepareNextAt(position) { success ->
|
||||
if (success) {
|
||||
notifyChange(PLAY_STATE_CHANGED)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPreviousPosition(force: Boolean): Int {
|
||||
|
@ -634,6 +630,8 @@ class MusicService : MediaBrowserServiceCompat(),
|
|||
|
||||
if (playbackManager.maybeSwitchToCrossFade(crossFadeDuration)) {
|
||||
restorePlaybackState(wasPlaying, progress)
|
||||
} else {
|
||||
playbackManager.setCrossFadeDuration(crossFadeDuration)
|
||||
}
|
||||
}
|
||||
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 {
|
||||
if (intent != null && intent.action != null) {
|
||||
handleIntent(mediaSession, intent)
|
||||
serviceScope.launch {
|
||||
restoreQueuesAndPositionIfNecessary()
|
||||
when (intent.action) {
|
||||
|
@ -757,15 +754,16 @@ class MusicService : MediaBrowserServiceCompat(),
|
|||
}
|
||||
|
||||
@Synchronized
|
||||
fun openTrackAndPrepareNextAt(position: Int): Boolean {
|
||||
fun openTrackAndPrepareNextAt(position: Int, completion: (success: Boolean) -> Unit) {
|
||||
this.position = position
|
||||
val prepared = openCurrent()
|
||||
if (prepared) {
|
||||
prepareNextImpl()
|
||||
openCurrent { success ->
|
||||
completion(success)
|
||||
if (success) {
|
||||
prepareNextImpl()
|
||||
}
|
||||
notifyChange(META_CHANGED)
|
||||
notHandledMetaChangedForCurrentTrack = false
|
||||
}
|
||||
notifyChange(META_CHANGED)
|
||||
notHandledMetaChangedForCurrentTrack = false
|
||||
return prepared
|
||||
}
|
||||
|
||||
fun pause(force: Boolean = false) {
|
||||
|
@ -776,11 +774,10 @@ class MusicService : MediaBrowserServiceCompat(),
|
|||
|
||||
@Synchronized
|
||||
fun play() {
|
||||
playbackManager.play(onNotInitialized = { playSongAt(getPosition()) }) {
|
||||
if (notHandledMetaChangedForCurrentTrack) {
|
||||
handleChangeInternal(META_CHANGED)
|
||||
notHandledMetaChangedForCurrentTrack = false
|
||||
}
|
||||
playbackManager.play { playSongAt(getPosition()) }
|
||||
if (notHandledMetaChangedForCurrentTrack) {
|
||||
handleChangeInternal(META_CHANGED)
|
||||
notHandledMetaChangedForCurrentTrack = false
|
||||
}
|
||||
notifyChange(PLAY_STATE_CHANGED)
|
||||
}
|
||||
|
@ -794,11 +791,18 @@ class MusicService : MediaBrowserServiceCompat(),
|
|||
}
|
||||
|
||||
fun playSongAt(position: Int) {
|
||||
serviceScope.launch(playerDispatcher) {
|
||||
if (openTrackAndPrepareNextAt(position)) {
|
||||
play()
|
||||
} else {
|
||||
showToast(R.string.unplayable_file)
|
||||
// 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 reduce the burden of main thread
|
||||
serviceScope.launch(if (playbackManager.isLocalPlayback) Default else Main) {
|
||||
openTrackAndPrepareNextAt(position) { success ->
|
||||
if (success) {
|
||||
play()
|
||||
} else {
|
||||
runOnUiThread {
|
||||
showToast(R.string.unplayable_file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -913,14 +917,15 @@ class MusicService : MediaBrowserServiceCompat(),
|
|||
originalPlayingQueue = ArrayList(restoredOriginalQueue)
|
||||
playingQueue = ArrayList(restoredQueue)
|
||||
position = restoredPosition
|
||||
withContext(playerDispatcher) {
|
||||
openCurrent()
|
||||
prepareNext()
|
||||
if (restoredPositionInTrack > 0) {
|
||||
seek(restoredPositionInTrack)
|
||||
withContext(Main) {
|
||||
openCurrent {
|
||||
prepareNext()
|
||||
if (restoredPositionInTrack > 0) {
|
||||
seek(restoredPositionInTrack)
|
||||
}
|
||||
notHandledMetaChangedForCurrentTrack = true
|
||||
sendChangeInternal(META_CHANGED)
|
||||
}
|
||||
notHandledMetaChangedForCurrentTrack = true
|
||||
sendChangeInternal(META_CHANGED)
|
||||
if (receivedHeadsetConnected) {
|
||||
play()
|
||||
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
|
||||
fun seek(millis: Int): Int {
|
||||
return try {
|
||||
|
@ -1024,8 +1018,10 @@ class MusicService : MediaBrowserServiceCompat(),
|
|||
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.albumName)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.title)
|
||||
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.duration)
|
||||
.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER,
|
||||
(getPosition() + 1).toLong())
|
||||
.putLong(
|
||||
MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER,
|
||||
(getPosition() + 1).toLong()
|
||||
)
|
||||
.putLong(MediaMetadataCompat.METADATA_KEY_YEAR, song.year.toLong())
|
||||
.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, null)
|
||||
.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
|
||||
// if we are loading it or it won't be updated in the notification
|
||||
updateMediaSessionMetaData(::updateMediaSessionPlaybackState)
|
||||
savePosition()
|
||||
savePositionInTrack()
|
||||
serviceScope.launch(IO) {
|
||||
savePosition()
|
||||
savePositionInTrack()
|
||||
val currentSong = currentSong
|
||||
HistoryStore.getInstance(this@MusicService).addSongId(currentSong.id)
|
||||
if (songPlayCountHelper.shouldBumpPlayCount()) {
|
||||
|
@ -1107,13 +1103,14 @@ class MusicService : MediaBrowserServiceCompat(),
|
|||
.bumpPlayCount(songPlayCountHelper.song.id)
|
||||
}
|
||||
songPlayCountHelper.notifySongChanged(currentSong)
|
||||
storage.saveSong(currentSong)
|
||||
}
|
||||
}
|
||||
QUEUE_CHANGED -> {
|
||||
mediaSession?.setQueueTitle(getString(R.string.now_playing_queue))
|
||||
mediaSession?.setQueue(playingQueue.toMediaSessionQueue())
|
||||
updateMediaSessionMetaData(::updateMediaSessionPlaybackState) // because playing queue size might have changed
|
||||
saveState()
|
||||
saveQueues()
|
||||
if (playingQueue.size > 0) {
|
||||
prepareNext()
|
||||
} else {
|
||||
|
@ -1164,18 +1161,15 @@ class MusicService : MediaBrowserServiceCompat(),
|
|||
}
|
||||
|
||||
@Synchronized
|
||||
private fun openCurrent(): Boolean {
|
||||
private fun openCurrent(completion: (success: Boolean) -> Unit) {
|
||||
val force = if (!trackEndedByCrossfade) {
|
||||
true
|
||||
} else {
|
||||
trackEndedByCrossfade = false
|
||||
false
|
||||
}
|
||||
return try {
|
||||
playbackManager.setDataSource(currentSong, force)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
false
|
||||
playbackManager.setDataSource(currentSong, force) { success ->
|
||||
completion(success)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1183,16 +1177,20 @@ class MusicService : MediaBrowserServiceCompat(),
|
|||
playbackManager.switchToLocalPlayback(this::restorePlaybackState)
|
||||
}
|
||||
|
||||
fun switchToRemotePlayback(castSession: CastSession) {
|
||||
playbackManager.switchToRemotePlayback(castSession, this::restorePlaybackState)
|
||||
fun switchToRemotePlayback(castPlayer: CastPlayer) {
|
||||
playbackManager.switchToRemotePlayback(castPlayer, this::restorePlaybackState)
|
||||
}
|
||||
|
||||
private fun restorePlaybackState(wasPlaying: Boolean, progress: Int) {
|
||||
playbackManager.setCallbacks(this)
|
||||
if (openTrackAndPrepareNextAt(position)) {
|
||||
seek(progress)
|
||||
if (wasPlaying) {
|
||||
play()
|
||||
openTrackAndPrepareNextAt(position) { success ->
|
||||
if (success) {
|
||||
seek(progress)
|
||||
if (wasPlaying) {
|
||||
play()
|
||||
} else {
|
||||
pause()
|
||||
}
|
||||
}
|
||||
}
|
||||
playbackManager.setCrossFadeDuration(crossFadeDuration)
|
||||
|
@ -1212,10 +1210,14 @@ class MusicService : MediaBrowserServiceCompat(),
|
|||
openQueue(playlistSongs, 0, true)
|
||||
}
|
||||
} else {
|
||||
showToast(R.string.playlist_is_empty, Toast.LENGTH_LONG)
|
||||
runOnUiThread {
|
||||
showToast(R.string.playlist_is_empty, Toast.LENGTH_LONG)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showToast(R.string.playlist_is_empty, Toast.LENGTH_LONG)
|
||||
runOnUiThread {
|
||||
showToast(R.string.playlist_is_empty, Toast.LENGTH_LONG)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1241,8 +1243,6 @@ class MusicService : MediaBrowserServiceCompat(),
|
|||
private fun releaseResources() {
|
||||
playerHandler?.removeCallbacksAndMessages(null)
|
||||
musicPlayerHandlerThread?.quitSafely()
|
||||
queueSaveHandler?.removeCallbacksAndMessages(null)
|
||||
queueSaveHandlerThread?.quitSafely()
|
||||
playbackManager.release()
|
||||
mediaSession?.release()
|
||||
}
|
||||
|
@ -1269,8 +1269,10 @@ class MusicService : MediaBrowserServiceCompat(),
|
|||
}
|
||||
|
||||
private fun saveQueues() {
|
||||
queueSaveHandler?.removeMessages(SAVE_QUEUES)
|
||||
queueSaveHandler?.sendEmptyMessage(SAVE_QUEUES)
|
||||
serviceScope.launch(IO) {
|
||||
MusicPlaybackQueueStore.getInstance(this@MusicService)
|
||||
.saveQueues(playingQueue, originalPlayingQueue)
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendChangeInternal(what: String) {
|
||||
|
@ -1308,13 +1310,27 @@ class MusicService : MediaBrowserServiceCompat(),
|
|||
}
|
||||
|
||||
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(
|
||||
this,
|
||||
"RetroMusicPlayer"
|
||||
BuildConfig.APPLICATION_ID,
|
||||
mediaButtonReceiverComponentName,
|
||||
mediaButtonReceiverPendingIntent
|
||||
)
|
||||
val mediaSessionCallback = MediaSessionCallback(this)
|
||||
mediaSession?.setCallback(mediaSessionCallback)
|
||||
mediaSession?.isActive = true
|
||||
mediaSession?.setMediaButtonReceiver(mediaButtonReceiverPendingIntent)
|
||||
}
|
||||
|
||||
inner class MusicBinder : Binder() {
|
||||
|
@ -1362,7 +1378,6 @@ class MusicService : MediaBrowserServiceCompat(),
|
|||
const val REPEAT_MODE_NONE = 0
|
||||
const val REPEAT_MODE_ALL = 1
|
||||
const val REPEAT_MODE_THIS = 2
|
||||
const val SAVE_QUEUES = 0
|
||||
private const val MEDIA_SESSION_ACTIONS = (PlaybackStateCompat.ACTION_PLAY
|
||||
or PlaybackStateCompat.ACTION_PAUSE
|
||||
or PlaybackStateCompat.ACTION_PLAY_PAUSE
|
||||
|
|
|
@ -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.playbackPitch
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil.playbackSpeed
|
||||
import com.google.android.gms.cast.framework.CastSession
|
||||
|
||||
|
||||
class PlaybackManager(val context: Context) {
|
||||
|
@ -47,16 +46,19 @@ class PlaybackManager(val context: Context) {
|
|||
playback?.callbacks = callbacks
|
||||
}
|
||||
|
||||
fun play(onNotInitialized: () -> Unit = {}, onPlay: () -> Unit = {}) {
|
||||
fun play(onNotInitialized: () -> Unit) {
|
||||
if (playback != null && !playback!!.isPlaying) {
|
||||
if (!playback!!.isInitialized) {
|
||||
onNotInitialized()
|
||||
} else {
|
||||
openAudioEffectSession()
|
||||
if (playbackLocation == PlaybackLocation.LOCAL) {
|
||||
AudioFader.startFadeAnimator(playback!!, true) {
|
||||
// Code when Animator Ends
|
||||
onPlay()
|
||||
if (playback is CrossFadePlayer) {
|
||||
if (!(playback as CrossFadePlayer).isCrossFading) {
|
||||
AudioFader.startFadeAnimator(playback!!, true)
|
||||
}
|
||||
} else {
|
||||
AudioFader.startFadeAnimator(playback!!, true)
|
||||
}
|
||||
}
|
||||
if (shouldSetSpeed) {
|
||||
|
@ -86,8 +88,12 @@ class PlaybackManager(val context: Context) {
|
|||
|
||||
fun seek(millis: Int): Int = playback!!.seek(millis)
|
||||
|
||||
fun setDataSource(song: Song, force: Boolean): Boolean {
|
||||
return playback?.setDataSource(song, force) == true
|
||||
fun setDataSource(
|
||||
song: Song,
|
||||
force: Boolean,
|
||||
completion: (success: Boolean) -> Unit,
|
||||
) {
|
||||
playback?.setDataSource(song, force, completion)
|
||||
}
|
||||
|
||||
fun setNextDataSource(trackUri: String) {
|
||||
|
@ -153,25 +159,22 @@ class PlaybackManager(val context: Context) {
|
|||
}
|
||||
|
||||
fun switchToRemotePlayback(
|
||||
castSession: CastSession,
|
||||
castPlayer: CastPlayer,
|
||||
onChange: (wasPlaying: Boolean, progress: Int) -> Unit,
|
||||
) {
|
||||
playbackLocation = PlaybackLocation.REMOTE
|
||||
switchToPlayback(CastPlayer(castSession), onChange)
|
||||
switchToPlayback(castPlayer, onChange)
|
||||
}
|
||||
|
||||
private fun switchToPlayback(
|
||||
playback: Playback,
|
||||
onChange: (wasPlaying: Boolean, progress: Int) -> Unit,
|
||||
) {
|
||||
val oldPlayback = playback
|
||||
val wasPlaying: Boolean = oldPlayback.isPlaying
|
||||
val progress: Int = oldPlayback.position()
|
||||
|
||||
val oldPlayback = this.playback
|
||||
val wasPlaying: Boolean = oldPlayback?.isPlaying == true
|
||||
val progress: Int = oldPlayback?.position() ?: 0
|
||||
this.playback = playback
|
||||
|
||||
oldPlayback.stop()
|
||||
|
||||
oldPlayback?.stop()
|
||||
onChange(wasPlaying, progress)
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,7 +25,9 @@ interface Playback {
|
|||
|
||||
val audioSessionId: Int
|
||||
|
||||
fun setDataSource(song: Song, force: Boolean):Boolean
|
||||
fun setDataSource(
|
||||
song: Song, force: Boolean, completion: (success: Boolean) -> Unit,
|
||||
)
|
||||
|
||||
fun setNextDataSource(path: String?)
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@ import androidx.viewpager.widget.ViewPager
|
|||
*/
|
||||
|
||||
class ParallaxPagerTransformer(private val id: Int) : ViewPager.PageTransformer {
|
||||
private var border = 0
|
||||
private var speed = 0.2f
|
||||
|
||||
override fun transformPage(page: View, position: Float) {
|
||||
|
@ -32,23 +31,13 @@ class ParallaxPagerTransformer(private val id: Int) : ViewPager.PageTransformer
|
|||
if (position > -1 && position < 1) {
|
||||
val width = parallaxView.width.toFloat()
|
||||
parallaxView.translationX = -(position * width * speed)
|
||||
val sc = (width - border) / width
|
||||
if (position == 0f) {
|
||||
scaleX = 1f
|
||||
scaleY = 1f
|
||||
} else {
|
||||
scaleX = sc
|
||||
scaleY = sc
|
||||
}
|
||||
scaleX = 1f
|
||||
scaleY = 1f
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setBorder(px: Int) {
|
||||
border = px
|
||||
}
|
||||
|
||||
fun setSpeed(speed: Float) {
|
||||
this.speed = speed
|
||||
}
|
||||
|
|
|
@ -117,7 +117,7 @@ object LyricUtil {
|
|||
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")
|
||||
}
|
||||
|
||||
|
@ -160,9 +160,9 @@ object LyricUtil {
|
|||
}
|
||||
|
||||
fun getEmbeddedSyncedLyrics(data: String): String? {
|
||||
val embeddedLyrics = try{
|
||||
AudioFileIO.read(File(data)).tagOrCreateDefault.getFirst(FieldKey.LYRICS)
|
||||
} catch(e: Exception){
|
||||
val embeddedLyrics = try {
|
||||
AudioFileIO.read(File(data)).tagOrCreateDefault.getFirst(FieldKey.LYRICS)
|
||||
} catch (e: Exception) {
|
||||
return null
|
||||
}
|
||||
return if (AbsSynchronizedLyrics.isSynchronized(embeddedLyrics)) {
|
||||
|
|
|
@ -39,12 +39,6 @@ object NavigationUtil {
|
|||
)
|
||||
}
|
||||
|
||||
fun goToProVersion(context: Context) {
|
||||
context.startActivity(
|
||||
Intent(context, PurchaseActivity::class.java), null
|
||||
)
|
||||
}
|
||||
|
||||
fun goToSupportDevelopment(activity: Activity) {
|
||||
activity.startActivity(
|
||||
Intent(activity, SupportDevelopmentActivity::class.java), null
|
||||
|
|
|
@ -115,7 +115,7 @@ object PreferenceUtil {
|
|||
putString(SAF_SDCARD_URI, value)
|
||||
}
|
||||
|
||||
val autoDownloadImagesPolicy
|
||||
private val autoDownloadImagesPolicy
|
||||
get() = sharedPreferences.getStringOrDefault(
|
||||
AUTO_DOWNLOAD_IMAGES_POLICY,
|
||||
"only_wifi"
|
||||
|
@ -242,8 +242,6 @@ object PreferenceUtil {
|
|||
|
||||
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 isPauseOnZeroVolume get() = sharedPreferences.getBoolean(PAUSE_ON_ZERO_VOLUME, false)
|
||||
|
@ -472,10 +470,10 @@ object PreferenceUtil {
|
|||
val tabTitleMode: Int
|
||||
get() {
|
||||
return when (sharedPreferences.getStringOrDefault(
|
||||
TAB_TEXT_MODE, "1"
|
||||
TAB_TEXT_MODE, "0"
|
||||
).toInt()) {
|
||||
1 -> BottomNavigationView.LABEL_VISIBILITY_LABELED
|
||||
0 -> BottomNavigationView.LABEL_VISIBILITY_AUTO
|
||||
1 -> BottomNavigationView.LABEL_VISIBILITY_LABELED
|
||||
2 -> BottomNavigationView.LABEL_VISIBILITY_SELECTED
|
||||
3 -> BottomNavigationView.LABEL_VISIBILITY_UNLABELED
|
||||
else -> BottomNavigationView.LABEL_VISIBILITY_LABELED
|
||||
|
@ -697,11 +695,11 @@ object PreferenceUtil {
|
|||
val isSnowFalling
|
||||
get() = sharedPreferences.getBoolean(SNOWFALL, false)
|
||||
|
||||
val lyricsType: LyricsType
|
||||
val lyricsType: CoverLyricsType
|
||||
get() = if (sharedPreferences.getString(LYRICS_TYPE, "0") == "0") {
|
||||
LyricsType.REPLACE_COVER
|
||||
CoverLyricsType.REPLACE_COVER
|
||||
} else {
|
||||
LyricsType.OVER_COVER
|
||||
CoverLyricsType.OVER_COVER
|
||||
}
|
||||
|
||||
var playbackSpeed
|
||||
|
@ -740,6 +738,6 @@ object PreferenceUtil {
|
|||
get() = sharedPreferences.getBoolean(SWIPE_DOWN_DISMISS, true)
|
||||
}
|
||||
|
||||
enum class LyricsType {
|
||||
enum class CoverLyricsType {
|
||||
REPLACE_COVER, OVER_COVER
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -19,14 +19,13 @@ import android.content.res.ColorStateList
|
|||
import android.util.AttributeSet
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||
import code.name.monkey.appthemehelper.util.NavigationViewUtil
|
||||
import code.name.monkey.retromusic.extensions.addAlpha
|
||||
import code.name.monkey.retromusic.extensions.setItemColors
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
import dev.chrisbanes.insetter.applyInsetter
|
||||
|
||||
class BottomNavigationBarTinted @JvmOverloads constructor(
|
||||
class TintedBottomNavigationView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0,
|
||||
|
@ -55,16 +54,7 @@ class BottomNavigationBarTinted @JvmOverloads constructor(
|
|||
if (!PreferenceUtil.materialYou) {
|
||||
val iconColor = ATHUtil.resolveColor(context, android.R.attr.colorControlNormal)
|
||||
val accentColor = ThemeStore.accentColor(context)
|
||||
NavigationViewUtil.setItemIconColors(
|
||||
this,
|
||||
ColorUtil.withAlpha(iconColor, 0.5f),
|
||||
accentColor
|
||||
)
|
||||
NavigationViewUtil.setItemTextColors(
|
||||
this,
|
||||
ColorUtil.withAlpha(iconColor, 0.5f),
|
||||
accentColor
|
||||
)
|
||||
setItemColors(iconColor, accentColor)
|
||||
itemRippleColor = ColorStateList.valueOf(accentColor.addAlpha(0.08F))
|
||||
itemActiveIndicatorColor = ColorStateList.valueOf(accentColor.addAlpha(0.12F))
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,5 @@
|
|||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:fillType="evenOdd"
|
||||
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" />
|
||||
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"/>
|
||||
</vector>
|
|
@ -3,18 +3,7 @@
|
|||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<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:strokeWidth="2"
|
||||
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"/>
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
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" />
|
||||
</vector>
|
||||
|
|
74
app/src/main/res/layout-land/fragment_adaptive_player.xml
Normal file
74
app/src/main/res/layout-land/fragment_adaptive_player.xml
Normal 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>
|
|
@ -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>
|
|
@ -112,7 +112,9 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:background="?attr/roundSelector"
|
||||
android:padding="16dp"
|
||||
android:scaleType="fitCenter"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/playPauseButton"
|
||||
app:layout_constraintStart_toEndOf="@+id/playPauseButton"
|
||||
app:layout_constraintTop_toTopOf="@+id/playPauseButton"
|
||||
|
@ -124,7 +126,9 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:background="?attr/roundSelector"
|
||||
android:padding="16dp"
|
||||
android:scaleType="fitCenter"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/playPauseButton"
|
||||
app:layout_constraintEnd_toStartOf="@+id/playPauseButton"
|
||||
app:layout_constraintTop_toTopOf="@+id/playPauseButton"
|
||||
|
@ -195,7 +199,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:gravity="center_vertical|right|end"
|
||||
android:gravity="center_vertical|end"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
|
@ -209,9 +213,11 @@
|
|||
android:id="@+id/songCurrentProgress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:gravity="center_vertical|left|end"
|
||||
android:paddingLeft="8dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginStart="16dp"
|
||||
android:gravity="center_vertical|start"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="0dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.navigationrail.NavigationRailView
|
||||
<code.name.monkey.retromusic.views.TintedNavigationRailView
|
||||
android:id="@+id/navigationView"
|
||||
style="@style/Widget.Material3.NavigationRailView"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
|
@ -31,52 +30,5 @@
|
|||
android:overScrollMode="@integer/overScrollMode"
|
||||
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.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -4,7 +4,9 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="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" />
|
||||
|
||||
|
@ -27,7 +29,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0"
|
||||
app:contentInsetLeft="0dp"
|
||||
app:contentInsetLeft="0dp"
|
||||
app:contentInsetStart="0dp"
|
||||
app:contentInsetStartWithNavigation="0dp"
|
||||
app:navigationIcon="@drawable/ic_keyboard_arrow_down_black"
|
||||
|
|
|
@ -25,64 +25,83 @@
|
|||
<include layout="@layout/shadow_statusbar_toolbar" />
|
||||
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/cardContainer"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
<include layout="@layout/status_bar" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
android:layout_marginTop="16dp"
|
||||
android:clickable="true"
|
||||
android:ellipsize="end"
|
||||
android:focusable="true"
|
||||
android:freezesText="true"
|
||||
android:paddingHorizontal="24dp"
|
||||
android:scrollHorizontally="true"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextViewHeadline6"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textStyle="bold"
|
||||
tools:text="@tools:sample/lorem" />
|
||||
|
||||
<include layout="@layout/status_bar" />
|
||||
<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" />
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/playerToolbar"
|
||||
style="@style/Toolbar"
|
||||
<FrameLayout
|
||||
android:id="@+id/cardContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
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"
|
||||
tools:layout="@layout/fragment_cover_lyrics" />
|
||||
|
||||
<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_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" />
|
||||
android:layout_gravity="bottom"
|
||||
tools:layout="@layout/fragment_card_blur_player_playback_controls" />
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
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" />
|
||||
|
||||
<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"
|
||||
tools:layout="@layout/fragment_cover_lyrics" />
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/playbackControlsFragment"
|
||||
android:name="code.name.monkey.retromusic.fragments.player.cardblur.CardBlurPlaybackControlsFragment"
|
||||
<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"
|
||||
tools:layout="@layout/fragment_card_blur_player_playback_controls" />
|
||||
</FrameLayout>
|
||||
app:navigationIcon="@drawable/ic_keyboard_arrow_down_black" />
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
|
@ -21,7 +21,7 @@
|
|||
android:layout_gravity="center_vertical"
|
||||
android:gravity="center"
|
||||
android:shadowColor="@color/md_black_1000"
|
||||
android:shadowRadius="4"
|
||||
android:shadowRadius="10"
|
||||
android:textAppearance="@style/TextViewHeadline5"
|
||||
android:textColor="@color/md_white_1000"
|
||||
android:visibility="gone"
|
||||
|
@ -34,7 +34,7 @@
|
|||
android:layout_gravity="center_vertical"
|
||||
android:gravity="center"
|
||||
android:shadowColor="@color/md_black_1000"
|
||||
android:shadowRadius="4"
|
||||
android:shadowRadius="10"
|
||||
android:textAppearance="@style/TextViewHeadline5"
|
||||
android:textColor="@color/md_white_1000"
|
||||
tools:text="@tools:sample/full_names[5]" />
|
||||
|
|
|
@ -127,8 +127,9 @@
|
|||
android:name="code.name.monkey.retromusic.fragments.player.CoverLyricsFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:elevation="20dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/playbackControlsFragment"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/playerToolbar" />
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
@ -58,7 +58,8 @@
|
|||
app:layout_constraintBottom_toBottomOf="@+id/playerAlbumCoverFragment"
|
||||
app:layout_constraintEnd_toEndOf="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>
|
||||
|
||||
|
|
|
@ -28,37 +28,56 @@
|
|||
android:layout_height="match_parent"
|
||||
android:background="?attr/colorSurface"
|
||||
android:gravity="start"
|
||||
app:contentInsetLeft="0dp"
|
||||
app:contentInsetLeft="0dp"
|
||||
app:contentInsetStart="0dp"
|
||||
app:contentInsetStartWithNavigation="0dp"
|
||||
app:navigationIcon="@drawable/ic_keyboard_backspace_black"
|
||||
app:subtitleTextAppearance="@style/TextViewCaption"
|
||||
app:title="@string/lyrics"
|
||||
app:titleMargin="0dp"
|
||||
app:titleMarginStart="0dp"
|
||||
app:titleTextAppearance="@style/TextViewSubtitle1">
|
||||
|
||||
<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>
|
||||
app:titleTextAppearance="@style/ToolbarTextAppearanceNormal" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/lyricsPager"
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="?attr/actionBarSize"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
android:layout_marginTop="?attr/actionBarSize">
|
||||
|
||||
<ScrollView
|
||||
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
|
||||
android:id="@+id/edit_button"
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -42,7 +42,7 @@
|
|||
|
||||
</FrameLayout>
|
||||
|
||||
<code.name.monkey.retromusic.views.BottomNavigationBarTinted
|
||||
<code.name.monkey.retromusic.views.TintedBottomNavigationView
|
||||
android:id="@+id/navigationView"
|
||||
style="@style/Widget.Material3.BottomNavigationView"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -125,5 +125,5 @@
|
|||
|
||||
<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>
|
|
@ -56,14 +56,6 @@
|
|||
android:title="@string/pref_title_toggle_toggle_headset"
|
||||
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
|
||||
android:defaultValue="false"
|
||||
android:key="bluetooth_playback"
|
||||
|
|
20
app/src/normal/AndroidManifest.xml
Normal file
20
app/src/normal/AndroidManifest.xml
Normal 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>
|
|
@ -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
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
|
@ -22,7 +8,7 @@ import android.view.MenuItem
|
|||
import code.name.monkey.appthemehelper.util.MaterialUtil
|
||||
import code.name.monkey.retromusic.App
|
||||
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.activities.base.AbsThemeActivity
|
||||
import code.name.monkey.retromusic.databinding.ActivityProVersionBinding
|
||||
|
@ -58,7 +44,7 @@ class PurchaseActivity : AbsThemeActivity(), BillingProcessor.IBillingHandler {
|
|||
restorePurchase()
|
||||
}
|
||||
binding.purchaseButton.setOnClickListener {
|
||||
billingProcessor.purchase(this@PurchaseActivity, PRO_VERSION_PRODUCT_ID)
|
||||
billingProcessor.purchase(this@PurchaseActivity, Constants.PRO_VERSION_PRODUCT_ID)
|
||||
}
|
||||
binding.bannerContainer.backgroundTintList =
|
||||
ColorStateList.valueOf(accentColor())
|
|
@ -3,6 +3,7 @@ package code.name.monkey.retromusic.activities.base
|
|||
import code.name.monkey.retromusic.cast.RetroSessionManagerListener
|
||||
import code.name.monkey.retromusic.cast.RetroWebServer
|
||||
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.CastSession
|
||||
import com.google.android.gms.common.ConnectionResult
|
||||
|
@ -37,7 +38,7 @@ abstract class AbsCastActivity : AbsSlidingMusicPanelActivity() {
|
|||
override fun onSessionStarted(castSession: CastSession, p1: String) {
|
||||
invalidateOptionsMenu()
|
||||
mCastSession = castSession
|
||||
MusicPlayerRemote.switchToRemotePlayback(castSession)
|
||||
MusicPlayerRemote.switchToRemotePlayback(CastPlayer(castSession))
|
||||
}
|
||||
|
||||
override fun onSessionEnded(castSession: CastSession, p1: Int) {
|
||||
|
@ -53,7 +54,7 @@ abstract class AbsCastActivity : AbsSlidingMusicPanelActivity() {
|
|||
invalidateOptionsMenu()
|
||||
mCastSession = castSession
|
||||
webServer.start()
|
||||
MusicPlayerRemote.switchToRemotePlayback(castSession)
|
||||
MusicPlayerRemote.switchToRemotePlayback(CastPlayer(castSession))
|
||||
}
|
||||
|
||||
override fun onSessionSuspended(castSession: CastSession, p1: Int) {
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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.media.RemoteMediaClient
|
||||
|
||||
class CastPlayer(castSession: CastSession) : Playback,
|
||||
RemoteMediaClient.Callback() {
|
||||
class CastPlayer(castSession: CastSession) : Playback, RemoteMediaClient.Callback() {
|
||||
|
||||
override val isInitialized: Boolean = true
|
||||
|
||||
|
@ -33,15 +32,19 @@ class CastPlayer(castSession: CastSession) : Playback,
|
|||
|
||||
override var callbacks: Playback.PlaybackCallbacks? = null
|
||||
|
||||
override fun setDataSource(song: Song, force: Boolean): Boolean {
|
||||
return try {
|
||||
override fun setDataSource(
|
||||
song: Song,
|
||||
force: Boolean,
|
||||
completion: (success: Boolean) -> Unit,
|
||||
) {
|
||||
try {
|
||||
val mediaLoadOptions =
|
||||
MediaLoadOptions.Builder().setPlayPosition(0).setAutoplay(true).build()
|
||||
remoteMediaClient?.load(song.toMediaInfo()!!, mediaLoadOptions)
|
||||
true
|
||||
completion(true)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
false
|
||||
completion(false)
|
||||
}
|
||||
}
|
||||
|
|
@ -28,7 +28,6 @@ object AppRater {
|
|||
private const val DAYS_UNTIL_PROMPT = 3//Min number of days
|
||||
private const val LAUNCHES_UNTIL_PROMPT = 5//Min number of launches
|
||||
|
||||
@JvmStatic
|
||||
fun appLaunched(context: Activity) {
|
||||
val prefs = context.getSharedPreferences(APP_RATING, 0)
|
||||
if (prefs.getBoolean(DO_NOT_SHOW_AGAIN, false)) {
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -2,11 +2,13 @@
|
|||
|
||||
buildscript {
|
||||
ext {
|
||||
kotlin_version = '1.6.21'
|
||||
navigation_version = '2.5.0-rc01'
|
||||
kotlin_version = '1.7.0'
|
||||
lifecycle_version='2.5.0-rc02'
|
||||
navigation_version = '2.5.0-rc02'
|
||||
mdc_version = '1.7.0-alpha02'
|
||||
preference_version = '1.2.0'
|
||||
appcompat_version = '1.4.2'
|
||||
core_version='1.8.0'
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
|
1
fastlane/metadata/android/en-US/changelogs/10596.txt
Normal file
1
fastlane/metadata/android/en-US/changelogs/10596.txt
Normal file
|
@ -0,0 +1 @@
|
|||
Added lyrics downloading
|
20
fastlane/metadata/android/en-US/full_description.txt
Normal file
20
fastlane/metadata/android/en-US/full_description.txt
Normal 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>
|
BIN
fastlane/metadata/android/en-US/images/logo.png
Normal file
BIN
fastlane/metadata/android/en-US/images/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 578 KiB After Width: | Height: | Size: 578 KiB |
Before Width: | Height: | Size: 692 KiB After Width: | Height: | Size: 692 KiB |
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue