Added F-Droid FOSS flavor
This commit is contained in:
parent
bc39d3a462
commit
2a5e6d7756
42 changed files with 243 additions and 360 deletions
|
@ -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,18 @@ 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() {}
|
||||
})
|
||||
|
||||
// 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 +75,7 @@ class App : Application() {
|
|||
}
|
||||
|
||||
fun isProVersion(): Boolean {
|
||||
return BuildConfig.DEBUG || instance?.billingProcessor!!.isPurchased(
|
||||
PRO_VERSION_PRODUCT_ID
|
||||
)
|
||||
return BuildConfig.DEBUG || instance?.billingManager!!.isProVersion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,119 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
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.R
|
||||
import code.name.monkey.retromusic.activities.base.AbsThemeActivity
|
||||
import code.name.monkey.retromusic.databinding.ActivityProVersionBinding
|
||||
import code.name.monkey.retromusic.extensions.accentColor
|
||||
import code.name.monkey.retromusic.extensions.setLightStatusBar
|
||||
import code.name.monkey.retromusic.extensions.setStatusBarColor
|
||||
import code.name.monkey.retromusic.extensions.showToast
|
||||
import com.anjlab.android.iab.v3.BillingProcessor
|
||||
import com.anjlab.android.iab.v3.PurchaseInfo
|
||||
|
||||
class PurchaseActivity : AbsThemeActivity(), BillingProcessor.IBillingHandler {
|
||||
|
||||
private lateinit var binding: ActivityProVersionBinding
|
||||
private lateinit var billingProcessor: BillingProcessor
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityProVersionBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
setStatusBarColor(Color.TRANSPARENT)
|
||||
setLightStatusBar(false)
|
||||
binding.toolbar.navigationIcon?.setTint(Color.WHITE)
|
||||
binding.toolbar.setNavigationOnClickListener { onBackPressed() }
|
||||
|
||||
binding.restoreButton.isEnabled = false
|
||||
binding.purchaseButton.isEnabled = false
|
||||
|
||||
billingProcessor = BillingProcessor(this, BuildConfig.GOOGLE_PLAY_LICENSING_KEY, this)
|
||||
|
||||
MaterialUtil.setTint(binding.purchaseButton, true)
|
||||
|
||||
binding.restoreButton.setOnClickListener {
|
||||
restorePurchase()
|
||||
}
|
||||
binding.purchaseButton.setOnClickListener {
|
||||
billingProcessor.purchase(this@PurchaseActivity, PRO_VERSION_PRODUCT_ID)
|
||||
}
|
||||
binding.bannerContainer.backgroundTintList =
|
||||
ColorStateList.valueOf(accentColor())
|
||||
}
|
||||
|
||||
private fun restorePurchase() {
|
||||
showToast(R.string.restoring_purchase)
|
||||
billingProcessor.loadOwnedPurchasesFromGoogleAsync(object :
|
||||
BillingProcessor.IPurchasesResponseListener {
|
||||
override fun onPurchasesSuccess() {
|
||||
onPurchaseHistoryRestored()
|
||||
}
|
||||
|
||||
override fun onPurchasesError() {
|
||||
showToast(R.string.could_not_restore_purchase)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onProductPurchased(productId: String, details: PurchaseInfo?) {
|
||||
showToast(R.string.thank_you)
|
||||
setResult(RESULT_OK)
|
||||
}
|
||||
|
||||
override fun onPurchaseHistoryRestored() {
|
||||
if (App.isProVersion()) {
|
||||
showToast(R.string.restored_previous_purchase_please_restart)
|
||||
setResult(RESULT_OK)
|
||||
} else {
|
||||
showToast(R.string.no_purchase_found)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBillingError(errorCode: Int, error: Throwable?) {
|
||||
Log.e(TAG, "Billing error: code = $errorCode", error)
|
||||
}
|
||||
|
||||
override fun onBillingInitialized() {
|
||||
binding.restoreButton.isEnabled = true
|
||||
binding.purchaseButton.isEnabled = true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
android.R.id.home -> finish()
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
billingProcessor.release()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG: String = "PurchaseActivity"
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,93 +0,0 @@
|
|||
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 com.google.android.gms.cast.framework.CastContext
|
||||
import com.google.android.gms.cast.framework.CastSession
|
||||
import com.google.android.gms.common.ConnectionResult
|
||||
import com.google.android.gms.common.GoogleApiAvailability
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
|
||||
abstract class AbsCastActivity : AbsSlidingMusicPanelActivity() {
|
||||
|
||||
private var mCastSession: CastSession? = null
|
||||
private val sessionManager by lazy {
|
||||
CastContext.getSharedInstance(this).sessionManager
|
||||
}
|
||||
|
||||
private val webServer: RetroWebServer by inject()
|
||||
|
||||
private val playServicesAvailable: Boolean by lazy {
|
||||
try {
|
||||
GoogleApiAvailability
|
||||
.getInstance().isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private val sessionManagerListener by lazy {
|
||||
object : RetroSessionManagerListener {
|
||||
override fun onSessionStarting(castSession: CastSession) {
|
||||
webServer.start()
|
||||
}
|
||||
|
||||
override fun onSessionStarted(castSession: CastSession, p1: String) {
|
||||
invalidateOptionsMenu()
|
||||
mCastSession = castSession
|
||||
MusicPlayerRemote.switchToRemotePlayback(castSession)
|
||||
}
|
||||
|
||||
override fun onSessionEnded(castSession: CastSession, p1: Int) {
|
||||
invalidateOptionsMenu()
|
||||
if (mCastSession == castSession) {
|
||||
mCastSession = null
|
||||
}
|
||||
MusicPlayerRemote.switchToLocalPlayback()
|
||||
webServer.stop()
|
||||
}
|
||||
|
||||
override fun onSessionResumed(castSession: CastSession, p1: Boolean) {
|
||||
invalidateOptionsMenu()
|
||||
mCastSession = castSession
|
||||
webServer.start()
|
||||
MusicPlayerRemote.switchToRemotePlayback(castSession)
|
||||
}
|
||||
|
||||
override fun onSessionSuspended(castSession: CastSession, p1: Int) {
|
||||
invalidateOptionsMenu()
|
||||
if (mCastSession == castSession) {
|
||||
mCastSession = null
|
||||
}
|
||||
MusicPlayerRemote.switchToLocalPlayback()
|
||||
webServer.stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (playServicesAvailable) {
|
||||
sessionManager.addSessionManagerListener(
|
||||
sessionManagerListener,
|
||||
CastSession::class.java
|
||||
)
|
||||
if (mCastSession == null) {
|
||||
mCastSession = sessionManager.currentCastSession
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
if (playServicesAvailable) {
|
||||
sessionManager.removeSessionManagerListener(
|
||||
sessionManagerListener,
|
||||
CastSession::class.java
|
||||
)
|
||||
mCastSession = null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
package code.name.monkey.retromusic.cast
|
||||
|
||||
import androidx.core.net.toUri
|
||||
import code.name.monkey.retromusic.cast.RetroWebServer.Companion.MIME_TYPE_AUDIO
|
||||
import code.name.monkey.retromusic.cast.RetroWebServer.Companion.PART_COVER_ART
|
||||
import code.name.monkey.retromusic.cast.RetroWebServer.Companion.PART_SONG
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.util.RetroUtil
|
||||
import com.google.android.gms.cast.MediaInfo
|
||||
import com.google.android.gms.cast.MediaInfo.STREAM_TYPE_BUFFERED
|
||||
import com.google.android.gms.cast.MediaMetadata
|
||||
import com.google.android.gms.cast.MediaMetadata.*
|
||||
import com.google.android.gms.common.images.WebImage
|
||||
import java.net.MalformedURLException
|
||||
import java.net.URL
|
||||
|
||||
object CastHelper {
|
||||
|
||||
private const val CAST_MUSIC_METADATA_ID = "metadata_id"
|
||||
private const val CAST_MUSIC_METADATA_ALBUM_ID = "metadata_album_id"
|
||||
private const val CAST_URL_PROTOCOL = "http"
|
||||
|
||||
fun Song.toMediaInfo(): MediaInfo? {
|
||||
val song = this
|
||||
val baseUrl: URL
|
||||
try {
|
||||
baseUrl = URL(CAST_URL_PROTOCOL, RetroUtil.getIpAddress(true), SERVER_PORT, "")
|
||||
} catch (e: MalformedURLException) {
|
||||
return null
|
||||
}
|
||||
|
||||
val songUrl = "$baseUrl/$PART_SONG?id=${song.id}"
|
||||
val albumArtUrl = "$baseUrl/$PART_COVER_ART?id=${song.albumId}"
|
||||
val musicMetadata = MediaMetadata(MEDIA_TYPE_MUSIC_TRACK).apply {
|
||||
putInt(CAST_MUSIC_METADATA_ID, song.id.toInt())
|
||||
putInt(CAST_MUSIC_METADATA_ALBUM_ID, song.albumId.toInt())
|
||||
putString(KEY_TITLE, song.title)
|
||||
putString(KEY_ARTIST, song.artistName)
|
||||
putString(KEY_ALBUM_TITLE, song.albumName)
|
||||
putInt(KEY_TRACK_NUMBER, song.trackNumber)
|
||||
addImage(WebImage(albumArtUrl.toUri()))
|
||||
}
|
||||
return MediaInfo.Builder(songUrl).apply {
|
||||
setStreamType(STREAM_TYPE_BUFFERED)
|
||||
setContentType(MIME_TYPE_AUDIO)
|
||||
setMetadata(musicMetadata)
|
||||
setStreamDuration(song.duration)
|
||||
}.build()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package code.name.monkey.retromusic.cast
|
||||
|
||||
import com.google.android.gms.cast.framework.CastSession
|
||||
import com.google.android.gms.cast.framework.SessionManagerListener
|
||||
|
||||
interface RetroSessionManagerListener : SessionManagerListener<CastSession> {
|
||||
override fun onSessionResuming(p0: CastSession, p1: String) {}
|
||||
|
||||
override fun onSessionStartFailed(p0: CastSession, p1: Int) {}
|
||||
|
||||
override fun onSessionResumeFailed(p0: CastSession, p1: Int) {}
|
||||
|
||||
override fun onSessionEnding(castSession: CastSession) {}
|
||||
}
|
|
@ -1,127 +0,0 @@
|
|||
package code.name.monkey.retromusic.cast
|
||||
|
||||
import android.content.Context
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import fi.iki.elonen.NanoHTTPD
|
||||
import fi.iki.elonen.NanoHTTPD.Response.Status
|
||||
import java.io.*
|
||||
|
||||
|
||||
const val SERVER_PORT = 9090
|
||||
|
||||
class RetroWebServer(val context: Context) : NanoHTTPD(SERVER_PORT) {
|
||||
companion object {
|
||||
private const val MIME_TYPE_IMAGE = "image/jpg"
|
||||
const val MIME_TYPE_AUDIO = "audio/mp3"
|
||||
|
||||
const val PART_COVER_ART = "coverart"
|
||||
const val PART_SONG = "song"
|
||||
const val PARAM_ID = "id"
|
||||
}
|
||||
|
||||
override fun serve(session: IHTTPSession?): Response {
|
||||
if (session?.uri?.contains(PART_COVER_ART) == true) {
|
||||
val albumId = session.parameters?.get(PARAM_ID)?.get(0) ?: return errorResponse()
|
||||
val albumArtUri = MusicUtil.getMediaStoreAlbumCoverUri(albumId.toLong())
|
||||
val fis: InputStream?
|
||||
try {
|
||||
fis = context.contentResolver.openInputStream(albumArtUri)
|
||||
} catch (e: FileNotFoundException) {
|
||||
return errorResponse()
|
||||
}
|
||||
return newChunkedResponse(Status.OK, MIME_TYPE_IMAGE, fis)
|
||||
} else if (session?.uri?.contains(PART_SONG) == true) {
|
||||
val songId = session.parameters?.get(PARAM_ID)?.get(0) ?: return errorResponse()
|
||||
val songUri = MusicUtil.getSongFileUri(songId.toLong())
|
||||
val songPath = MusicUtil.getSongFilePath(context, songUri)
|
||||
val song = File(songPath)
|
||||
return serveFile(session.headers!!, song, MIME_TYPE_AUDIO)
|
||||
}
|
||||
return newFixedLengthResponse(Status.NOT_FOUND, MIME_PLAINTEXT, "Not Found")
|
||||
}
|
||||
|
||||
private fun serveFile(
|
||||
header: MutableMap<String, String>, file: File,
|
||||
mime: String
|
||||
): Response {
|
||||
var res: Response
|
||||
try {
|
||||
// Support (simple) skipping:
|
||||
var startFrom: Long = 0
|
||||
var endAt: Long = -1
|
||||
// The value of header range will be bytes=0-1024 something like this
|
||||
// We get the value of from Bytes i.e. startFrom and toBytes i.e. endAt below
|
||||
var range = header["range"]
|
||||
if (range != null) {
|
||||
if (range.startsWith("bytes=")) {
|
||||
range = range.substring("bytes=".length)
|
||||
val minus = range.indexOf('-')
|
||||
try {
|
||||
if (minus > 0) {
|
||||
startFrom = range
|
||||
.substring(0, minus).toLong()
|
||||
endAt = range.substring(minus + 1).toLong()
|
||||
}
|
||||
} catch (ignored: NumberFormatException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Chunked Response is used when serving audio file
|
||||
// Change return code and add Content-Range header when skipping is
|
||||
// requested
|
||||
val fileLen = file.length()
|
||||
if (range != null && startFrom >= 0) {
|
||||
if (startFrom >= fileLen) {
|
||||
res = newFixedLengthResponse(
|
||||
Status.RANGE_NOT_SATISFIABLE,
|
||||
MIME_PLAINTEXT, ""
|
||||
)
|
||||
res.addHeader("Content-Range", "bytes 0-0/$fileLen")
|
||||
} else {
|
||||
if (endAt < 0) {
|
||||
endAt = fileLen - 1
|
||||
}
|
||||
var newLen = endAt - startFrom + 1
|
||||
if (newLen < 0) {
|
||||
newLen = 0
|
||||
}
|
||||
val dataLen = newLen
|
||||
val fis: FileInputStream = object : FileInputStream(file) {
|
||||
@Throws(IOException::class)
|
||||
override fun available(): Int {
|
||||
return dataLen.toInt()
|
||||
}
|
||||
}
|
||||
fis.skip(startFrom)
|
||||
res = newChunkedResponse(
|
||||
Status.PARTIAL_CONTENT, mime,
|
||||
fis
|
||||
)
|
||||
res.addHeader("Content-Length", "" + dataLen)
|
||||
res.addHeader(
|
||||
"Content-Range", "bytes " + startFrom + "-"
|
||||
+ endAt + "/" + fileLen
|
||||
)
|
||||
}
|
||||
} else {
|
||||
res = newFixedLengthResponse(
|
||||
Status.OK, mime,
|
||||
file.inputStream(), file.length()
|
||||
)
|
||||
res.addHeader("Accept-Ranges", "bytes")
|
||||
res.addHeader("Content-Length", "" + fileLen)
|
||||
}
|
||||
} catch (ioe: IOException) {
|
||||
res = newFixedLengthResponse(
|
||||
Status.FORBIDDEN,
|
||||
MIME_PLAINTEXT, "FORBIDDEN: Reading file failed."
|
||||
)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
private fun errorResponse(message: String = "Error Occurred"): Response {
|
||||
return newFixedLengthResponse(Status.INTERNAL_ERROR, MIME_PLAINTEXT, message)
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
@ -456,8 +456,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() {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
package code.name.monkey.retromusic.service
|
||||
|
||||
import code.name.monkey.retromusic.cast.CastHelper.toMediaInfo
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.service.playback.Playback
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil.playbackSpeed
|
||||
import com.google.android.gms.cast.MediaLoadOptions
|
||||
import com.google.android.gms.cast.MediaSeekOptions
|
||||
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() {
|
||||
|
||||
override val isInitialized: Boolean = true
|
||||
|
||||
private val remoteMediaClient: RemoteMediaClient? = castSession.remoteMediaClient
|
||||
|
||||
init {
|
||||
remoteMediaClient?.registerCallback(this)
|
||||
remoteMediaClient?.setPlaybackRate(playbackSpeed.toDouble().coerceIn(0.5, 2.0))
|
||||
}
|
||||
|
||||
private var isActuallyPlaying = false
|
||||
|
||||
override val isPlaying: Boolean
|
||||
get() {
|
||||
return remoteMediaClient?.isPlaying == true || isActuallyPlaying
|
||||
}
|
||||
|
||||
override val audioSessionId: Int = 0
|
||||
|
||||
override var callbacks: Playback.PlaybackCallbacks? = null
|
||||
|
||||
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)
|
||||
completion(true)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
completion(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setNextDataSource(path: String?) {}
|
||||
|
||||
override fun start(): Boolean {
|
||||
isActuallyPlaying = true
|
||||
remoteMediaClient?.play()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
isActuallyPlaying = false
|
||||
remoteMediaClient?.stop()
|
||||
}
|
||||
|
||||
override fun release() {
|
||||
stop()
|
||||
}
|
||||
|
||||
override fun pause(): Boolean {
|
||||
isActuallyPlaying = false
|
||||
remoteMediaClient?.pause()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun duration(): Int {
|
||||
return remoteMediaClient?.streamDuration?.toInt() ?: 0
|
||||
}
|
||||
|
||||
override fun position(): Int {
|
||||
return remoteMediaClient?.approximateStreamPosition?.toInt() ?: 0
|
||||
}
|
||||
|
||||
override fun seek(whereto: Int): Int {
|
||||
remoteMediaClient?.seek(MediaSeekOptions.Builder().setPosition(whereto.toLong()).build())
|
||||
return 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) {
|
||||
remoteMediaClient?.setPlaybackRate(speed.toDouble().coerceIn(0.5, 2.0))
|
||||
}
|
||||
|
||||
override fun onStatusUpdated() {
|
||||
when (remoteMediaClient?.playerState) {
|
||||
MediaStatus.PLAYER_STATE_IDLE -> {
|
||||
val idleReason = remoteMediaClient.idleReason
|
||||
if (idleReason == MediaStatus.IDLE_REASON_FINISHED) {
|
||||
callbacks?.onTrackEnded()
|
||||
}
|
||||
}
|
||||
MediaStatus.PLAYER_STATE_PLAYING, MediaStatus.PLAYER_STATE_PAUSED -> {
|
||||
callbacks?.onPlayStateChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,10 +15,10 @@
|
|||
|
||||
package code.name.monkey.retromusic.service
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
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
|
||||
|
@ -62,8 +62,7 @@ class MediaButtonIntentReceiver : MediaButtonReceiver() {
|
|||
private var mClickCounter = 0
|
||||
private var mLastClickTime: Long = 0
|
||||
|
||||
@SuppressLint("HandlerLeak") // false alarm, handler is already static
|
||||
private val mHandler = object : Handler() {
|
||||
private val mHandler = object : Handler(Looper.getMainLooper()) {
|
||||
|
||||
override fun handleMessage(msg: Message) {
|
||||
when (msg.what) {
|
||||
|
|
|
@ -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
|
||||
|
@ -1171,8 +1170,8 @@ 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) {
|
||||
|
|
|
@ -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) {
|
||||
|
@ -160,11 +159,11 @@ 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(
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
package code.name.monkey.retromusic.util
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import com.google.android.play.core.review.ReviewManagerFactory
|
||||
|
||||
object AppRater {
|
||||
private const val DO_NOT_SHOW_AGAIN = "do_not_show_again"// Package Name
|
||||
private const val APP_RATING = "app_rating"// Package Name
|
||||
private const val LAUNCH_COUNT = "launch_count"// Package Name
|
||||
private const val DATE_FIRST_LAUNCH = "date_first_launch"// Package Name
|
||||
|
||||
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)) {
|
||||
return
|
||||
}
|
||||
|
||||
prefs.edit {
|
||||
|
||||
// Increment launch counter
|
||||
val launchCount = prefs.getLong(LAUNCH_COUNT, 0) + 1
|
||||
putLong(LAUNCH_COUNT, launchCount)
|
||||
|
||||
// Get date of first launch
|
||||
var dateFirstLaunch = prefs.getLong(DATE_FIRST_LAUNCH, 0)
|
||||
if (dateFirstLaunch == 0L) {
|
||||
dateFirstLaunch = System.currentTimeMillis()
|
||||
putLong(DATE_FIRST_LAUNCH, dateFirstLaunch)
|
||||
}
|
||||
|
||||
// Wait at least n days before opening
|
||||
if (launchCount >= LAUNCHES_UNTIL_PROMPT) {
|
||||
if (System.currentTimeMillis() >= dateFirstLaunch + DAYS_UNTIL_PROMPT * 24 * 60 * 60 * 1000) {
|
||||
//showRateDialog(context, editor)
|
||||
showPlayStoreReviewDialog(context, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showPlayStoreReviewDialog(context: Activity, editor: SharedPreferences.Editor) {
|
||||
val manager = ReviewManagerFactory.create(context)
|
||||
val flow = manager.requestReviewFlow()
|
||||
flow.addOnCompleteListener { request ->
|
||||
if (request.isSuccessful) {
|
||||
val reviewInfo = request.result
|
||||
val flowManager = manager.launchReviewFlow(context, reviewInfo)
|
||||
flowManager.addOnCompleteListener {
|
||||
if (it.isSuccessful) {
|
||||
editor.putBoolean(DO_NOT_SHOW_AGAIN, true)
|
||||
editor.commit()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue