This commit is contained in:
ocean 2024-05-29 14:36:27 +08:00
parent 40fb451fa4
commit 6565860cbb
13 changed files with 308 additions and 23 deletions

View File

@ -4,6 +4,8 @@ plugins {
id("kotlin-kapt") id("kotlin-kapt")
id("org.jetbrains.kotlin.plugin.serialization") id("org.jetbrains.kotlin.plugin.serialization")
id("kotlin-android") id("kotlin-android")
id("com.google.gms.google-services")
id("com.google.firebase.crashlytics")
} }
android { android {
@ -30,13 +32,13 @@ android {
"proguard-rules.pro" "proguard-rules.pro"
) )
} }
debug { // debug {
isMinifyEnabled = true // isMinifyEnabled = true
proguardFiles( // proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"), // getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro" // "proguard-rules.pro"
) // )
} // }
} }
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8
@ -97,4 +99,10 @@ dependencies {
//google //google
// implementation("com.google.android.gms:play-services-ads-identifier:18.0.1") // implementation("com.google.android.gms:play-services-ads-identifier:18.0.1")
// Import the Firebase BoM
implementation(platform("com.google.firebase:firebase-bom:32.3.1"))
implementation("com.google.firebase:firebase-analytics-ktx")
implementation("com.google.firebase:firebase-crashlytics-ktx")
implementation("com.google.firebase:firebase-config")
} }

29
app/google-services.json Normal file
View File

@ -0,0 +1,29 @@
{
"project_info": {
"project_number": "320083292372",
"project_id": "musiclax-and",
"storage_bucket": "musiclax-and.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:320083292372:android:047dd51c4c373acf9a8b41",
"android_client_info": {
"package_name": "relax.offline.mp3.music"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyCs8V_b7UYuUfcs_mAWIAbr06VZKBM-680"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}

View File

@ -41,6 +41,7 @@
</activity> </activity>
<activity <activity
android:name=".activity.MainActivity" android:name=".activity.MainActivity"
android:launchMode="singleTask"
android:screenOrientation="portrait" /> android:screenOrientation="portrait" />
<activity <activity
android:name=".activity.PrimaryActivity" android:name=".activity.PrimaryActivity"

View File

@ -20,10 +20,13 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import relax.offline.music.database.AppFavoriteDBManager import relax.offline.music.database.AppFavoriteDBManager
import relax.offline.music.firebase.RemoteConfig
import relax.offline.music.http.CommonIpInfoUtil import relax.offline.music.http.CommonIpInfoUtil
import relax.offline.music.http.UploadEventName import relax.offline.music.http.UploadEventName
import relax.offline.music.util.AppLifecycleHandler
import java.io.BufferedReader import java.io.BufferedReader
import java.io.InputStreamReader import java.io.InputStreamReader
import java.util.concurrent.atomic.AtomicBoolean
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
class App : Application() { class App : Application() {
@ -107,9 +110,15 @@ class App : Application() {
} }
} }
var isAdShowing: AtomicBoolean = AtomicBoolean(false)//是否正在广告界面
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
app = this app = this
AppLifecycleHandler(this)
CommonIpInfoUtil.shared.initIPInfo()
UploadEventName.shared.init(this)
RemoteConfig.instance.init(this)
initialize(this) initialize(this)
MediaControllerManager.init(this) MediaControllerManager.init(this)
LocalMediaControllerManager.init(this) LocalMediaControllerManager.init(this)
@ -121,8 +130,5 @@ class App : Application() {
initImportAudio() initImportAudio()
CacheManager.initializeCaches(this) CacheManager.initializeCaches(this)
DownloadUtil.getDownloadManager(this) DownloadUtil.getDownloadManager(this)
CommonIpInfoUtil.shared.initIPInfo()
UploadEventName.shared.init(this)
} }
} }

View File

@ -8,7 +8,7 @@ import relax.offline.music.databinding.ActivityLaunchBinding
class LaunchActivity : MoBaseActivity() { class LaunchActivity : MoBaseActivity() {
private lateinit var binding: ActivityLaunchBinding private lateinit var binding: ActivityLaunchBinding
private val totalTime = 3000L // 5秒 private val totalTime = 5000L // 5秒
private val interval = 50L // 更新间隔,毫秒 private val interval = 50L // 更新间隔,毫秒
private val steps = totalTime / interval private val steps = totalTime / interval
private val progressPerStep = 100f / steps.toFloat() private val progressPerStep = 100f / steps.toFloat()

View File

@ -1,7 +1,6 @@
package relax.offline.music.activity package relax.offline.music.activity
import android.content.Context import android.content.Context
import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
@ -32,7 +31,6 @@ import relax.offline.music.App
import relax.offline.music.R import relax.offline.music.R
import relax.offline.music.bean.FavoriteBean import relax.offline.music.bean.FavoriteBean
import relax.offline.music.bean.OfflineBean import relax.offline.music.bean.OfflineBean
import relax.offline.music.innertube.Innertube
import relax.offline.music.media.MediaControllerManager import relax.offline.music.media.MediaControllerManager
import relax.offline.music.sp.AppStore import relax.offline.music.sp.AppStore
import relax.offline.music.util.LogTag import relax.offline.music.util.LogTag
@ -235,7 +233,7 @@ abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope
thumbnail = mediaItem.mediaMetadata.artworkUri.toString(), thumbnail = mediaItem.mediaMetadata.artworkUri.toString(),
isOffline = true isOffline = true
) )
LogTag.LogD(Innertube.TAG, "insertOfflineBean bean->${bean}") LogTag.LogD(TAG, "insertOfflineBean bean->${bean}")
App.appOfflineDBManager.insertOfflineBean(bean) App.appOfflineDBManager.insertOfflineBean(bean)
} }
@ -247,14 +245,30 @@ abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope
thumbnail = mediaItem.mediaMetadata.artworkUri.toString(), thumbnail = mediaItem.mediaMetadata.artworkUri.toString(),
isFavorite = true isFavorite = true
) )
LogTag.LogD(Innertube.TAG, "insertFavoriteBean bean->${bean}") LogTag.LogD(TAG, "insertFavoriteBean bean->${bean}")
App.appFavoriteDBManager.insertFavoriteBean(bean) App.appFavoriteDBManager.insertFavoriteBean(bean)
} }
fun withPermission(): Boolean { fun withPermission(): Boolean {
//先判断当前配置的开关是否为true为false的话就直接进入A
LogTag.LogD(TAG, "withPermission shouldEnterMusicPage->${appStore.shouldEnterMusicPage}")
if (!appStore.shouldEnterMusicPage) {
return false
}
// 不允许的国家代码 // 不允许的国家代码
val restrictedCountries = setOf("CN", "HK", "TW", "JP", "KR", "GB", "CH", "BE", "MO", "SG") val restrictedCountries = setOf(
// "CN",
// "HK",
"TW",
"JP",
"KR",
"GB",
"CH",
"BE",
"MO",
"SG")
// 检查是否包含当前的国家代码 // 检查是否包含当前的国家代码
LogTag.LogD(TAG, "withPermission ipCountryCode->${appStore.ipCountryCode}")
if (appStore.ipCountryCode in restrictedCountries) { if (appStore.ipCountryCode in restrictedCountries) {
return false return false
} }
@ -275,9 +289,9 @@ abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope
//525 Singapore (Republic of) //525 Singapore (Republic of)
val restrictedCountryCodes = val restrictedCountryCodes =
setOf( setOf(
"460", // "460",
"461", // "461",
"454", // "454",
"466", "466",
"440", "440",
"441", "441",
@ -290,6 +304,7 @@ abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope
"525" "525"
) )
val currentCountryCode = getCountryCode(this) val currentCountryCode = getCountryCode(this)
LogTag.LogD(TAG, "withPermission currentCountryCode->${currentCountryCode}")
return currentCountryCode !in restrictedCountryCodes return currentCountryCode !in restrictedCountryCodes
} }
} }

View File

@ -31,10 +31,8 @@ class AppOfflineDBManager private constructor(context: Context) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val offlineBean = getOfflineBeanByID(bean.videoId) val offlineBean = getOfflineBeanByID(bean.videoId)
if (offlineBean == null) { if (offlineBean == null) {
LogTag.LogD(LogTag.VO_TEST_ONLY,"insertOfflineBean")
dao.insertOfflineBean(bean) dao.insertOfflineBean(bean)
} else { } else {
LogTag.LogD(LogTag.VO_TEST_ONLY,"updateOfflineBean")
dao.updateOfflineBean(bean) dao.updateOfflineBean(bean)
} }
} }

View File

@ -0,0 +1,7 @@
package relax.offline.music.firebase
object Constants {
const val KEY_SHOULD_ENTER_MUSIC_PAGE = "key_should_enter_music_page"
const val DEFAULT_SHOULD_ENTER_MUSIC_PAGE = false
}

View File

@ -0,0 +1,146 @@
package relax.offline.music.firebase
import android.annotation.SuppressLint
import android.app.Application
import android.content.Context
import android.os.Handler
import android.os.Message
import android.text.TextUtils
import com.google.firebase.remoteconfig.ConfigUpdate
import com.google.firebase.remoteconfig.ConfigUpdateListener
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import com.google.firebase.remoteconfig.FirebaseRemoteConfigException
import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings
import com.google.firebase.remoteconfig.FirebaseRemoteConfigValue
import relax.offline.music.App
import relax.offline.music.BuildConfig
import relax.offline.music.sp.AppStore
import relax.offline.music.util.LogTag
import java.lang.ref.WeakReference
class RemoteConfig {
private val TAG = LogTag.VO_TEST_ONLY
private var ctx: Context? = null
private var mFirebaseRemoteConfig: FirebaseRemoteConfig? = null
//配置是否初始化成功
private var isInit = false
//上次获取数据的时间
private var lastFetchTime: Long = 0
private val handler = MHandler(this)
companion object {
const val MSG_REFRESH_CONFIG = 1
val instance: RemoteConfig by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
RemoteConfig()
}
}
fun init(ctx: Application) {
this.ctx = ctx
initConfig()
fetchConfig()
onConfigUpdate()
}
private fun initConfig() {
var intervalTime = (60 * 10).toLong()
//如果是开发状态,则将提取时间缩短
if (BuildConfig.DEBUG) {
intervalTime = (60 * 5).toLong()
}
mFirebaseRemoteConfig = FirebaseRemoteConfig.getInstance()
val configSettings =
FirebaseRemoteConfigSettings.Builder() //默认值12小时的最短提取间隔如果在间隔内取值则优先取上次的结果
.setMinimumFetchIntervalInSeconds(intervalTime)
.build()
mFirebaseRemoteConfig!!.setConfigSettingsAsync(configSettings)
}
private fun onConfigUpdate() {
mFirebaseRemoteConfig!!.addOnConfigUpdateListener(object : ConfigUpdateListener {
override fun onUpdate(configUpdate: ConfigUpdate) {
LogTag.LogD(TAG, "Updated keys: " + configUpdate.updatedKeys)
if (configUpdate.updatedKeys.contains(Constants.KEY_SHOULD_ENTER_MUSIC_PAGE)) {
mFirebaseRemoteConfig!!.activate().addOnCompleteListener { task ->
if (task.isSuccessful) {
updateData("onConfigUpdate", mFirebaseRemoteConfig!!.all)
}
}
}
}
override fun onError(error: FirebaseRemoteConfigException) {
LogTag.LogD(TAG, "Config update error with code: " + error.code)
}
})
}
@SuppressLint("LongLogTag")
private fun fetchConfig() {
//这里可能会抛出异常 FirebaseRemoteConfigFetchThrottledException
try {
mFirebaseRemoteConfig!!.fetchAndActivate().addOnCompleteListener { task ->
if (task.isSuccessful) {
isInit = true
lastFetchTime = System.currentTimeMillis()
updateData("fetchAndActivate", mFirebaseRemoteConfig!!.all)
//24小时后重新再去获取
handler.removeMessages(MSG_REFRESH_CONFIG)
handler.sendEmptyMessageDelayed(
MSG_REFRESH_CONFIG,
(1000 * 60 * 60 * 24).toLong()
)
} else {
//这里需要重新再去获取
handler.removeMessages(MSG_REFRESH_CONFIG)
handler.sendEmptyMessageDelayed(MSG_REFRESH_CONFIG, (1000 * 60 * 15).toLong())
}
}
} catch (ignore: Exception) {
}
}
private fun updateData(from: String, all: Map<String, FirebaseRemoteConfigValue>) {
val appStore = AppStore(App.app)
for ((key, value) in all) {
try {
LogTag.LogD(
TAG,
"from = " + from + "Key = " + key + " Value = " + value.asString()
)
if (TextUtils.equals(
Constants.KEY_SHOULD_ENTER_MUSIC_PAGE, key
)
) {
val shouldEnterMusicPage = value.asBoolean()
appStore.shouldEnterMusicPage = shouldEnterMusicPage
}
} catch (ignore: Exception) {
}
}
}
private class MHandler(remoteConfig: RemoteConfig) : Handler() {
private val weakReference: WeakReference<RemoteConfig>
init {
weakReference = WeakReference(remoteConfig)
}
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
val remoteConfig = weakReference.get()
if (remoteConfig?.ctx != null) {
if (msg.what == MSG_REFRESH_CONFIG) {
remoteConfig.fetchConfig()
}
}
}
}
}

View File

@ -1,6 +1,7 @@
package relax.offline.music.sp package relax.offline.music.sp
import android.content.Context import android.content.Context
import relax.offline.music.firebase.Constants
import relax.offline.music.sp.store.Store import relax.offline.music.sp.store.Store
import relax.offline.music.sp.store.asStoreProvider import relax.offline.music.sp.store.asStoreProvider
import relax.offline.music.util.PlayMode import relax.offline.music.util.PlayMode
@ -32,7 +33,7 @@ class AppStore(context: Context) {
defaultValue = "" defaultValue = ""
) )
var appUUID : String by store.string( var appUUID: String by store.string(
key = APP_UUID, key = APP_UUID,
defaultValue = "" defaultValue = ""
) )
@ -42,10 +43,16 @@ class AppStore(context: Context) {
defaultValue = "" defaultValue = ""
) )
var firstOpenIsSucceed : Boolean by store.boolean( var firstOpenIsSucceed: Boolean by store.boolean(
key = FIRST_OPEN_IS_SUCCEED, key = FIRST_OPEN_IS_SUCCEED,
defaultValue = false defaultValue = false
) )
var shouldEnterMusicPage: Boolean by store.boolean(
key = Constants.KEY_SHOULD_ENTER_MUSIC_PAGE,
defaultValue = Constants.DEFAULT_SHOULD_ENTER_MUSIC_PAGE
)
companion object { companion object {
private const val FILE_NAME = "music_oo_app" private const val FILE_NAME = "music_oo_app"
const val SEARCH_HISTORY = "search_history" const val SEARCH_HISTORY = "search_history"

View File

@ -0,0 +1,9 @@
package relax.offline.music.util
import com.google.firebase.analytics.FirebaseAnalytics
object AnalysisUtil {
private lateinit var firebaseAnalytics: FirebaseAnalytics
}

View File

@ -0,0 +1,57 @@
package relax.offline.music.util
import android.app.Activity
import android.app.Application
import android.content.Intent
import android.os.Bundle
import android.os.SystemClock
import relax.offline.music.App
import relax.offline.music.activity.LaunchActivity
/**
* 一个处理应用程序生命周期事件并管理启动页显示的类
*
* @param application 应用程序实例
*/
class AppLifecycleHandler(private val application: Application) : Application.ActivityLifecycleCallbacks {
private var activityReferences = 0
private var isActivityChangingConfigurations = false
private var lastPausedTime: Long = 0
private val intervalTime = 5000L
init {
application.registerActivityLifecycleCallbacks(this)
}
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}
override fun onActivityStarted(activity: Activity) {
if (++activityReferences == 1 && !isActivityChangingConfigurations) {
// 应用进入前台
val currentTime = SystemClock.elapsedRealtime()
val isAdShowing = App.app.isAdShowing.get()
//间隔时间是否满足当前不是启动页当前不是广告show
if (currentTime - lastPausedTime > intervalTime && activity !is LaunchActivity && !isAdShowing) {
val intent = Intent(activity, LaunchActivity::class.java)
activity.startActivity(intent)
}
}
}
override fun onActivityResumed(activity: Activity) {}
override fun onActivityPaused(activity: Activity) {}
override fun onActivityStopped(activity: Activity) {
isActivityChangingConfigurations = activity.isChangingConfigurations
if (--activityReferences == 0 && !isActivityChangingConfigurations) {
// 应用进入后台
lastPausedTime = SystemClock.elapsedRealtime()
}
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
override fun onActivityDestroyed(activity: Activity) {}
}

View File

@ -3,4 +3,6 @@ plugins {
id("com.android.application") version "8.2.1" apply false id("com.android.application") version "8.2.1" apply false
id("org.jetbrains.kotlin.android") version "1.9.22" apply false id("org.jetbrains.kotlin.android") version "1.9.22" apply false
id("org.jetbrains.kotlin.plugin.serialization") version "1.7.20" apply false id("org.jetbrains.kotlin.plugin.serialization") version "1.7.20" apply false
id("com.google.gms.google-services") version "4.3.15" apply false
id("com.google.firebase.crashlytics") version "2.9.5" apply false
} }