mirror of
https://github.com/meowarex/rl-mobile.git
synced 2026-06-18 05:23:12 +10:00
Overhaul Update System <3
This commit is contained in:
@@ -142,7 +142,7 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
|
||||
val targetDir = when (componentType) {
|
||||
"injector" -> paths.customInjectorsDir
|
||||
"tidal" -> paths.customTidalApksDir
|
||||
"patches" -> paths.customPatchesDir
|
||||
else -> {
|
||||
Log.w(BuildConfig.TAG, "Extra $EXTRA_COMPONENT_TYPE is not a valid value!")
|
||||
|
||||
@@ -26,7 +26,7 @@ class PathManager(
|
||||
|
||||
val customComponentsDir = patchingDir.resolve("custom")
|
||||
|
||||
val customInjectorsDir = customComponentsDir.resolve("injector")
|
||||
val customTidalApksDir = customComponentsDir.resolve("tidal")
|
||||
|
||||
val customPatchesDir = customComponentsDir.resolve("patches")
|
||||
|
||||
@@ -54,7 +54,7 @@ class PathManager(
|
||||
.resolve("patches")
|
||||
.resolve("$version.zip")
|
||||
|
||||
fun customInjectors() = customInjectorsDir.listFiles()?.asList() ?: emptyList()
|
||||
fun customTidalApks() = customTidalApksDir.listFiles()?.asList() ?: emptyList()
|
||||
|
||||
fun customSmaliPatches() = customPatchesDir.listFiles()?.asList() ?: emptyList()
|
||||
}
|
||||
|
||||
@@ -15,4 +15,8 @@ class PreferencesManager(preferences: SharedPreferences) : BasePreferenceManager
|
||||
var showNetworkWarning by booleanPreference("show_network_warning", true)
|
||||
var showPlayProtectWarning by booleanPreference("show_play_protect_warning", true)
|
||||
var autoUpdateCheck by booleanPreference("auto_update_check", true)
|
||||
|
||||
var lastSeenManagerVersion by stringPreference("last_seen_manager_version", "")
|
||||
var lastSeenPatchesVersion by stringPreference("last_seen_patches_version", "")
|
||||
var lastSeenTidalVersionCode by intPreference("last_seen_tidal_version_code", -1)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ class TidalPatchRunner(
|
||||
RestoreDownloadsStep(),
|
||||
|
||||
// Download
|
||||
DownloadTidalStep(),
|
||||
DownloadTidalStep(options.customTidalApk),
|
||||
DownloadPatchesStep(options.customPatches),
|
||||
CopyDependenciesStep(),
|
||||
|
||||
|
||||
+25
-2
@@ -5,12 +5,17 @@ import com.meowarex.rlmobile.R
|
||||
import com.meowarex.rlmobile.manager.PathManager
|
||||
import com.meowarex.rlmobile.patcher.StepRunner
|
||||
import com.meowarex.rlmobile.patcher.steps.base.DownloadStep
|
||||
import com.meowarex.rlmobile.patcher.steps.base.StepState
|
||||
import com.meowarex.rlmobile.patcher.steps.prepare.FetchInfoStep
|
||||
import com.meowarex.rlmobile.ui.screens.componentopts.PatchComponent
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import java.io.FileNotFoundException
|
||||
|
||||
@Stable
|
||||
class DownloadTidalStep : DownloadStep<Int>(), KoinComponent {
|
||||
class DownloadTidalStep(
|
||||
private val custom: PatchComponent?,
|
||||
) : DownloadStep<Int>(), KoinComponent {
|
||||
private val paths: PathManager by inject()
|
||||
|
||||
override val localizedName = R.string.patch_step_dl_tidal_apk
|
||||
@@ -22,5 +27,23 @@ class DownloadTidalStep : DownloadStep<Int>(), KoinComponent {
|
||||
container.getStep<FetchInfoStep>().data.tidalApkUrl
|
||||
|
||||
override fun getStoredFile(container: StepRunner) =
|
||||
paths.cachedTidalApk(getVersion(container))
|
||||
custom?.getFile(paths) ?: paths.cachedTidalApk(getVersion(container))
|
||||
|
||||
override suspend fun execute(container: StepRunner) {
|
||||
if (custom != null) {
|
||||
container.log("Using custom TIDAL APK with version ${custom.version} imported ${custom.timestamp}")
|
||||
|
||||
if (!custom.getFile(paths).exists()) {
|
||||
throw FileNotFoundException(
|
||||
"Selected custom TIDAL APK does not exist on disk! If this is an update, " +
|
||||
"updates cannot occur when the originally selected custom component has been deleted."
|
||||
)
|
||||
}
|
||||
|
||||
state = StepState.Skipped
|
||||
return
|
||||
}
|
||||
|
||||
super.execute(container)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.meowarex.rlmobile.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun Tag(text: String) {
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.primaryContainer,
|
||||
shape = RoundedCornerShape(6.dp),
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 3.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -44,27 +44,27 @@ private data class ComponentOptionsParameters(
|
||||
private class ComponentOptionsParametersProvider : PreviewParameterProvider<ComponentOptionsParameters> {
|
||||
private val components = persistentListOf(
|
||||
PatchComponent(
|
||||
type = PatchComponent.Type.Injector,
|
||||
type = PatchComponent.Type.TidalApk,
|
||||
version = SemVer(1, 2, 3),
|
||||
timestamp = Clock.System.now(),
|
||||
),
|
||||
PatchComponent(
|
||||
type = PatchComponent.Type.Injector,
|
||||
type = PatchComponent.Type.TidalApk,
|
||||
version = SemVer(2, 3, 1),
|
||||
timestamp = Clock.System.now() - 10.minutes,
|
||||
),
|
||||
PatchComponent(
|
||||
type = PatchComponent.Type.Injector,
|
||||
type = PatchComponent.Type.TidalApk,
|
||||
version = SemVer(2, 3, 1),
|
||||
timestamp = Clock.System.now() - 1.days,
|
||||
),
|
||||
PatchComponent(
|
||||
type = PatchComponent.Type.Injector,
|
||||
type = PatchComponent.Type.TidalApk,
|
||||
version = SemVer(0, 0, 1),
|
||||
timestamp = Clock.System.now() - 10.hours,
|
||||
),
|
||||
PatchComponent(
|
||||
type = PatchComponent.Type.Injector,
|
||||
type = PatchComponent.Type.TidalApk,
|
||||
version = SemVer(3, 0, 2),
|
||||
timestamp = Clock.System.now() - 7.days,
|
||||
),
|
||||
@@ -72,7 +72,7 @@ private class ComponentOptionsParametersProvider : PreviewParameterProvider<Comp
|
||||
|
||||
override val values = sequenceOf(
|
||||
ComponentOptionsParameters(
|
||||
componentType = PatchComponent.Type.Injector,
|
||||
componentType = PatchComponent.Type.TidalApk,
|
||||
components = components,
|
||||
selected = null,
|
||||
),
|
||||
|
||||
+7
-7
@@ -30,8 +30,8 @@ private fun PatchOptionsScreenPreview(
|
||||
packageName = parameters.packageName,
|
||||
packageNameState = parameters.packageNameState,
|
||||
setPackageName = {},
|
||||
customInjector = parameters.customInjector,
|
||||
onSelectCustomInjector = {},
|
||||
customTidalApk = parameters.customTidalApk,
|
||||
onSelectCustomTidalApk = {},
|
||||
customPatches = parameters.customPatches,
|
||||
onSelectCustomPatches = {},
|
||||
enabledPatchCount = KnownPatch.All.size,
|
||||
@@ -51,7 +51,7 @@ private data class PatchOptionsParameters(
|
||||
val appNameIsError: Boolean,
|
||||
val packageName: String,
|
||||
val packageNameState: PackageNameState,
|
||||
val customInjector: PatchComponent?,
|
||||
val customTidalApk: PatchComponent?,
|
||||
val customPatches: PatchComponent?,
|
||||
val isConfigValid: Boolean,
|
||||
)
|
||||
@@ -66,7 +66,7 @@ private class PatchOptionsParametersProvider : PreviewParameterProvider<PatchOpt
|
||||
appNameIsError = false,
|
||||
packageName = PatchOptions.Default.packageName,
|
||||
packageNameState = PackageNameState.Ok,
|
||||
customInjector = null,
|
||||
customTidalApk = null,
|
||||
customPatches = null,
|
||||
isConfigValid = true,
|
||||
),
|
||||
@@ -78,7 +78,7 @@ private class PatchOptionsParametersProvider : PreviewParameterProvider<PatchOpt
|
||||
appNameIsError = true,
|
||||
packageName = "a b",
|
||||
packageNameState = PackageNameState.Invalid,
|
||||
customInjector = null,
|
||||
customTidalApk = null,
|
||||
customPatches = null,
|
||||
isConfigValid = false,
|
||||
),
|
||||
@@ -90,8 +90,8 @@ private class PatchOptionsParametersProvider : PreviewParameterProvider<PatchOpt
|
||||
appNameIsError = false,
|
||||
packageName = PatchOptions.Default.packageName,
|
||||
packageNameState = PackageNameState.Taken,
|
||||
customInjector = PatchComponent(
|
||||
type = PatchComponent.Type.Injector,
|
||||
customTidalApk = PatchComponent(
|
||||
type = PatchComponent.Type.TidalApk,
|
||||
version = SemVer(1, 2, 3),
|
||||
timestamp = Clock.System.now(),
|
||||
),
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ class ComponentOptionsModel(
|
||||
*/
|
||||
suspend fun refreshComponents(type: PatchComponent.Type) {
|
||||
val files = when (type) {
|
||||
PatchComponent.Type.Injector -> paths.customInjectors()
|
||||
PatchComponent.Type.TidalApk -> paths.customTidalApks()
|
||||
PatchComponent.Type.Patches -> paths.customSmaliPatches()
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -34,8 +34,8 @@ data class PatchComponent(
|
||||
@Parcelize
|
||||
@Serializable
|
||||
enum class Type : Parcelable {
|
||||
@SerialName("injector")
|
||||
Injector,
|
||||
@SerialName("tidal")
|
||||
TidalApk,
|
||||
|
||||
@SerialName("patches")
|
||||
Patches,
|
||||
@@ -47,11 +47,11 @@ data class PatchComponent(
|
||||
*/
|
||||
fun getFile(paths: PathManager): File {
|
||||
val dir = when (type) {
|
||||
Type.Injector -> paths.customInjectorsDir
|
||||
Type.TidalApk -> paths.customTidalApksDir
|
||||
Type.Patches -> paths.customPatchesDir
|
||||
}
|
||||
val ext = when (type) {
|
||||
Type.Injector -> "dex"
|
||||
Type.TidalApk -> "apk"
|
||||
Type.Patches -> "zip"
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import cafe.adriel.voyager.core.model.screenModelScope
|
||||
import com.github.diamondminer88.zip.ZipReader
|
||||
import com.meowarex.rlmobile.BuildConfig
|
||||
import com.meowarex.rlmobile.R
|
||||
import com.meowarex.rlmobile.manager.PreferencesManager
|
||||
import com.meowarex.rlmobile.network.models.GithubCommit
|
||||
import com.meowarex.rlmobile.network.models.RLBuildInfo
|
||||
import com.meowarex.rlmobile.network.services.RadiantLyricsGithubService
|
||||
@@ -28,6 +29,7 @@ import com.meowarex.rlmobile.patcher.InstallMetadata
|
||||
import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptions
|
||||
import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptionsScreen
|
||||
import com.meowarex.rlmobile.ui.util.TidalVersion
|
||||
import com.meowarex.rlmobile.ui.widgets.managerupdate.VersionDelta
|
||||
import com.meowarex.rlmobile.util.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
@@ -39,11 +41,15 @@ class HomeModel(
|
||||
private val application: Application,
|
||||
private val github: RadiantLyricsGithubService,
|
||||
private val json: Json,
|
||||
private val prefs: PreferencesManager,
|
||||
) : ScreenModel {
|
||||
|
||||
var state by mutableStateOf<HomeState>(HomeState.Loading)
|
||||
private set
|
||||
|
||||
var managerUpdateDeltas by mutableStateOf<List<VersionDelta>?>(null)
|
||||
private set
|
||||
|
||||
val commits = Pager(PagingConfig(pageSize = 30)) {
|
||||
CommitsPagingSource(github)
|
||||
}.flow.cachedIn(screenModelScope)
|
||||
@@ -51,10 +57,103 @@ class HomeModel(
|
||||
private val refreshingLock = Mutex()
|
||||
private var remoteDataJson: RLBuildInfo? = null
|
||||
|
||||
private val initialPrefManagerVersion: String = prefs.lastSeenManagerVersion
|
||||
private val initialPrefPatchesVersion: String = prefs.lastSeenPatchesVersion
|
||||
private val initialPrefTidalVersionCode: Int = prefs.lastSeenTidalVersionCode
|
||||
|
||||
private var managerUpdateChecked = false
|
||||
|
||||
init {
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun dismissManagerUpdate() {
|
||||
managerUpdateDeltas = null
|
||||
commitVersionPrefs()
|
||||
}
|
||||
|
||||
private fun commitVersionPrefs() {
|
||||
prefs.lastSeenManagerVersion = BuildConfig.VERSION_NAME
|
||||
remoteDataJson?.let {
|
||||
prefs.lastSeenPatchesVersion = it.patchesVersion.toString()
|
||||
prefs.lastSeenTidalVersionCode = it.tidalVersionCode
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeCheckManagerUpdate(installedPkg: PackageInfo?) {
|
||||
if (managerUpdateChecked) return
|
||||
managerUpdateChecked = true
|
||||
|
||||
val installMetadata = installedPkg?.packageName?.let(::loadInstallMetadata)
|
||||
val current = BuildConfig.VERSION_NAME
|
||||
|
||||
val previousManager = initialPrefManagerVersion.ifEmpty {
|
||||
installMetadata?.managerVersion?.toString().orEmpty()
|
||||
}
|
||||
|
||||
when {
|
||||
previousManager.isEmpty() -> commitVersionPrefs()
|
||||
previousManager == current -> commitVersionPrefs()
|
||||
else -> managerUpdateDeltas =
|
||||
buildDeltas(previousManager, current, installMetadata, installedPkg)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDeltas(
|
||||
previousManager: String,
|
||||
currentManager: String,
|
||||
installMetadata: InstallMetadata?,
|
||||
installedPkg: PackageInfo?,
|
||||
): List<VersionDelta> = buildList {
|
||||
add(
|
||||
VersionDelta(
|
||||
label = application.getString(R.string.manager_update_row_manager),
|
||||
iconRes = R.drawable.ic_sparkle,
|
||||
from = previousManager,
|
||||
to = currentManager,
|
||||
tag = application.getString(R.string.manager_update_tag_complete),
|
||||
)
|
||||
)
|
||||
|
||||
val remote = remoteDataJson
|
||||
val currentPatches = remote?.patchesVersion?.toString()
|
||||
val previousPatches = initialPrefPatchesVersion.ifEmpty {
|
||||
installMetadata?.patchesVersion?.toString().orEmpty()
|
||||
}
|
||||
val patchesFrom = previousPatches.takeIf { it.isNotEmpty() }
|
||||
val patchesTo = currentPatches ?: previousPatches.ifEmpty { "?" }
|
||||
add(
|
||||
VersionDelta(
|
||||
label = application.getString(R.string.manager_update_row_patches),
|
||||
iconRes = R.drawable.ic_extension,
|
||||
from = patchesFrom,
|
||||
to = patchesTo,
|
||||
tag = if (patchesFrom != null && patchesFrom != patchesTo)
|
||||
application.getString(R.string.manager_update_tag_available) else null,
|
||||
)
|
||||
)
|
||||
|
||||
val currentTidal = remote?.tidalVersionCode
|
||||
@Suppress("DEPRECATION")
|
||||
val installedTidalVersionCode = installedPkg?.versionCode ?: -1
|
||||
val previousTidal = if (initialPrefTidalVersionCode > 0) initialPrefTidalVersionCode
|
||||
else installedTidalVersionCode
|
||||
val tidalFrom = previousTidal.takeIf { it > 0 }?.toString()
|
||||
val tidalTo = currentTidal?.toString()
|
||||
?: previousTidal.takeIf { it > 0 }?.toString()
|
||||
?: "?"
|
||||
add(
|
||||
VersionDelta(
|
||||
label = application.getString(R.string.manager_update_row_tidal),
|
||||
iconRes = R.drawable.ic_music_note,
|
||||
from = tidalFrom,
|
||||
to = tidalTo,
|
||||
tag = if (tidalFrom != null && tidalFrom != tidalTo)
|
||||
application.getString(R.string.manager_update_tag_available) else null,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun refresh(delay: Boolean = false) = screenModelScope.launchIO {
|
||||
if (refreshingLock.isLocked) return@launchIO
|
||||
if (delay) {
|
||||
@@ -75,6 +174,7 @@ class HomeModel(
|
||||
install = install,
|
||||
latestTidalVersionCode = latest,
|
||||
)
|
||||
maybeCheckManagerUpdate(pkg)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,7 +189,7 @@ class HomeModel(
|
||||
openAppInfo(current.packageName)
|
||||
}
|
||||
|
||||
fun createReinstallScreen(): PatchOptionsScreen? {
|
||||
fun createRepatchScreen(): PatchOptionsScreen? {
|
||||
val current = (state as? HomeState.Loaded)?.install ?: return null
|
||||
return createPrefilledPatchOptsScreen(current.packageName)
|
||||
}
|
||||
@@ -112,20 +212,21 @@ class HomeModel(
|
||||
}
|
||||
|
||||
fun createPrefilledPatchOptsScreen(packageName: String): PatchOptionsScreen {
|
||||
val metadata = try {
|
||||
val applicationInfo = application.packageManager.getApplicationInfo(packageName, 0)
|
||||
val metadataFile = ZipReader(applicationInfo.publicSourceDir)
|
||||
.use { it.openEntry("rlmobile.json")?.read() }
|
||||
metadataFile?.let { json.decodeFromStream<InstallMetadata>(it.inputStream()) }
|
||||
} catch (t: Throwable) {
|
||||
Log.w(BuildConfig.TAG, "Failed to parse install metadata for $packageName", t)
|
||||
null
|
||||
}
|
||||
|
||||
val patchOptions = metadata?.options ?: PatchOptions.Default.copy(packageName = packageName)
|
||||
val patchOptions = loadInstallMetadata(packageName)?.options
|
||||
?: PatchOptions.Default.copy(packageName = packageName)
|
||||
return PatchOptionsScreen(prefilledOptions = patchOptions)
|
||||
}
|
||||
|
||||
private fun loadInstallMetadata(packageName: String): InstallMetadata? = try {
|
||||
val applicationInfo = application.packageManager.getApplicationInfo(packageName, 0)
|
||||
val metadataBytes = ZipReader(applicationInfo.publicSourceDir)
|
||||
.use { it.openEntry("rlmobile.json")?.read() }
|
||||
metadataBytes?.let { json.decodeFromStream<InstallMetadata>(it.inputStream()) }
|
||||
} catch (t: Throwable) {
|
||||
Log.w(BuildConfig.TAG, "Failed to parse install metadata for $packageName", t)
|
||||
null
|
||||
}
|
||||
|
||||
private fun fetchInstalled(): PackageInfo? = application.packageManager
|
||||
.getInstalledPackages(PackageManager.GET_META_DATA)
|
||||
.firstOrNull { it.applicationInfo?.metaData?.containsKey("isRadiantLyrics") == true }
|
||||
@@ -138,7 +239,8 @@ class HomeModel(
|
||||
return InstallData(
|
||||
name = pm.getApplicationLabel(info).toString(),
|
||||
packageName = packageName,
|
||||
isUpToDate = isInstallationUpToDate(this),
|
||||
tidalUpToDate = isTidalUpToDate(this),
|
||||
patchesUpToDate = isPatchesUpToDate(this),
|
||||
icon = pm.getApplicationIcon(info).toBitmap().asImageBitmap().let(::BitmapPainter),
|
||||
version = TidalVersion.Existing(
|
||||
type = TidalVersion.parseVersionType(versionCode),
|
||||
@@ -170,11 +272,14 @@ class HomeModel(
|
||||
)
|
||||
}
|
||||
|
||||
private fun isInstallationUpToDate(pkg: PackageInfo): Boolean? {
|
||||
private fun isTidalUpToDate(pkg: PackageInfo): Boolean? {
|
||||
val remote = remoteDataJson ?: return null
|
||||
@Suppress("DEPRECATION") val versionCode = pkg.versionCode
|
||||
if (remote.tidalVersionCode != versionCode) return false
|
||||
return remote.tidalVersionCode == versionCode
|
||||
}
|
||||
|
||||
private fun isPatchesUpToDate(pkg: PackageInfo): Boolean? {
|
||||
val remote = remoteDataJson ?: return null
|
||||
val apkPath = pkg.applicationInfo?.publicSourceDir ?: return false
|
||||
val installMetadata = try {
|
||||
val mf = ZipReader(apkPath).use { it.openEntry("rlmobile.json")?.read() } ?: return false
|
||||
@@ -182,8 +287,6 @@ class HomeModel(
|
||||
} catch (t: Throwable) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (installMetadata.options.customPatches != null) return true
|
||||
return remote.patchesVersion == installMetadata.patchesVersion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import com.meowarex.rlmobile.ui.screens.home.components.CommitList
|
||||
import com.meowarex.rlmobile.ui.screens.logs.LogsListScreen
|
||||
import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptionsScreen
|
||||
import com.meowarex.rlmobile.ui.screens.settings.SettingsScreen
|
||||
import com.meowarex.rlmobile.ui.widgets.managerupdate.ManagerUpdateDialog
|
||||
import com.meowarex.rlmobile.util.*
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
@@ -106,9 +107,9 @@ class HomeScreen : Screen, Parcelable {
|
||||
state = state,
|
||||
commits = model.commits,
|
||||
onInstall = { navigator.pushOnce(PatchOptionsScreen()) },
|
||||
onReinstall = {
|
||||
onRepatch = {
|
||||
scope.launchIO {
|
||||
val screen = model.createReinstallScreen() ?: return@launchIO
|
||||
val screen = model.createRepatchScreen() ?: return@launchIO
|
||||
mainThread { navigator.push(screen) }
|
||||
}
|
||||
},
|
||||
@@ -116,6 +117,13 @@ class HomeScreen : Screen, Parcelable {
|
||||
onInfo = model::openCurrentAppInfo,
|
||||
)
|
||||
}
|
||||
|
||||
model.managerUpdateDeltas?.let { deltas ->
|
||||
ManagerUpdateDialog(
|
||||
deltas = deltas,
|
||||
onDismiss = model::dismissManagerUpdate,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,12 +134,13 @@ private fun ColumnScope.HomeContent(
|
||||
state: HomeState.Loaded,
|
||||
commits: kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<com.meowarex.rlmobile.network.models.GithubCommit>>,
|
||||
onInstall: () -> Unit,
|
||||
onReinstall: () -> Unit,
|
||||
onRepatch: () -> Unit,
|
||||
onLaunch: () -> Unit,
|
||||
onInfo: () -> Unit,
|
||||
) {
|
||||
val install = state.install
|
||||
val currentVersionName = install?.version?.let { "v${it.toString()}" }
|
||||
val currentVersionName = (install?.version as? com.meowarex.rlmobile.ui.util.TidalVersion.Existing)
|
||||
?.let { "v${it.name} (build ${it.code})" }
|
||||
val latestVersionName = state.latestTidalVersionCode?.let { "build $it" }
|
||||
|
||||
val fallbackPainter = if (install?.icon == null) {
|
||||
@@ -181,8 +190,17 @@ private fun ColumnScope.HomeContent(
|
||||
}
|
||||
}
|
||||
|
||||
val patchesBehind = install != null && install.patchesUpToDate == false
|
||||
val tidalBehind = install != null && install.tidalUpToDate == false
|
||||
AnimatedVisibility(visible = patchesBehind || tidalBehind) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
if (patchesBehind) UpdateTag(text = "New Patches!")
|
||||
if (tidalBehind) UpdateTag(text = "TIDAL Update!")
|
||||
}
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = if (install == null) onInstall else onReinstall,
|
||||
onClick = if (install == null) onInstall else onRepatch,
|
||||
enabled = state.latestTidalVersionCode != null,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
@@ -190,7 +208,7 @@ private fun ColumnScope.HomeContent(
|
||||
state.latestTidalVersionCode == null -> "Loading…"
|
||||
install == null -> "Install"
|
||||
install.isUpToDate == false -> "Update"
|
||||
else -> "Reinstall"
|
||||
else -> "Repatch"
|
||||
}
|
||||
Text(
|
||||
text = label,
|
||||
@@ -222,5 +240,20 @@ private fun ColumnScope.HomeContent(
|
||||
|
||||
ElevatedCard(modifier = Modifier.fillMaxSize()) {
|
||||
CommitList(commits = commits.collectAsLazyPagingItems())
|
||||
'}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UpdateTag(text: String) {
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.primaryContainer,
|
||||
shape = RoundedCornerShape(6.dp),
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 3.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,5 +10,12 @@ data class InstallData(
|
||||
val packageName: String,
|
||||
val version: TidalVersion,
|
||||
val icon: BitmapPainter,
|
||||
val isUpToDate: Boolean?,
|
||||
)
|
||||
val tidalUpToDate: Boolean?,
|
||||
val patchesUpToDate: Boolean?,
|
||||
) {
|
||||
val isUpToDate: Boolean?
|
||||
get() = when {
|
||||
tidalUpToDate == null || patchesUpToDate == null -> null
|
||||
else -> tidalUpToDate && patchesUpToDate
|
||||
}
|
||||
}
|
||||
|
||||
+3
-6
@@ -25,10 +25,7 @@ data class PatchOptions(
|
||||
*/
|
||||
val debuggable: Boolean,
|
||||
|
||||
/**
|
||||
* A custom build of injector that was used rather than the latest.
|
||||
*/
|
||||
val customInjector: PatchComponent? = null,
|
||||
val customTidalApk: PatchComponent? = null,
|
||||
|
||||
/**
|
||||
* A custom smali patches bundle that was used rather than the latest.
|
||||
@@ -42,9 +39,9 @@ data class PatchOptions(
|
||||
appName = "TIDAL",
|
||||
packageName = "com.aspiro.tidal",
|
||||
debuggable = false,
|
||||
customInjector = null,
|
||||
customTidalApk = null,
|
||||
customPatches = null,
|
||||
disabledPatches = emptySet(),
|
||||
disabledPatches = (KnownPatch.DebugMenuUnlock.fileNames + KnownPatch.EnableLegacyUi.fileNames).toSet(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+6
-6
@@ -81,16 +81,16 @@ class PatchOptionsModel(
|
||||
val enabledPatchCount: Int
|
||||
get() = KnownPatch.All.count { isPatchEnabled(it) }
|
||||
|
||||
var customInjector by mutableStateOf<PatchComponent?>(null)
|
||||
var customTidalApk by mutableStateOf<PatchComponent?>(null)
|
||||
private set
|
||||
var customPatches by mutableStateOf<PatchComponent?>(null)
|
||||
private set
|
||||
|
||||
fun selectCustomInjector(navigator: Navigator) = screenModelScope.launch {
|
||||
customInjector = navigator.pushForResult(
|
||||
fun selectCustomTidalApk(navigator: Navigator) = screenModelScope.launch {
|
||||
customTidalApk = navigator.pushForResult(
|
||||
ComponentOptionsScreen(
|
||||
default = customInjector,
|
||||
componentType = PatchComponent.Type.Injector,
|
||||
default = customTidalApk,
|
||||
componentType = PatchComponent.Type.TidalApk,
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -120,7 +120,7 @@ class PatchOptionsModel(
|
||||
appName = appName,
|
||||
packageName = packageName,
|
||||
debuggable = debuggable,
|
||||
customInjector = customInjector,
|
||||
customTidalApk = customTidalApk,
|
||||
customPatches = customPatches,
|
||||
disabledPatches = disabledPatches,
|
||||
)
|
||||
|
||||
+10
-10
@@ -55,9 +55,9 @@ class PatchOptionsScreen(
|
||||
packageNameState = model.packageNameState,
|
||||
setPackageName = model::changePackageName,
|
||||
|
||||
customInjector = model.customInjector,
|
||||
customTidalApk = model.customTidalApk,
|
||||
customPatches = model.customPatches,
|
||||
onSelectCustomInjector = { model.selectCustomInjector(navigator) },
|
||||
onSelectCustomTidalApk = { model.selectCustomTidalApk(navigator) },
|
||||
onSelectCustomPatches = { model.selectCustomPatches(navigator) },
|
||||
|
||||
enabledPatchCount = model.enabledPatchCount,
|
||||
@@ -88,8 +88,8 @@ fun PatchOptionsScreenContent(
|
||||
packageNameState: PackageNameState,
|
||||
setPackageName: (String) -> Unit,
|
||||
|
||||
customInjector: PatchComponent?,
|
||||
onSelectCustomInjector: () -> Unit,
|
||||
customTidalApk: PatchComponent?,
|
||||
onSelectCustomTidalApk: () -> Unit,
|
||||
customPatches: PatchComponent?,
|
||||
onSelectCustomPatches: () -> Unit,
|
||||
|
||||
@@ -180,14 +180,14 @@ fun PatchOptionsScreenContent(
|
||||
)
|
||||
|
||||
IconPatchOption(
|
||||
icon = painterResource(R.drawable.ic_extension),
|
||||
name = stringResource(R.string.patchopts_custom_injector_title),
|
||||
description = stringResource(R.string.patchopts_custom_injector_desc),
|
||||
modifier = Modifier.clickable(onClick = onSelectCustomInjector),
|
||||
icon = painterResource(R.drawable.ic_music_note),
|
||||
name = stringResource(R.string.patchopts_custom_tidal_apk_title),
|
||||
description = stringResource(R.string.patchopts_custom_tidal_apk_desc),
|
||||
modifier = Modifier.clickable(onClick = onSelectCustomTidalApk),
|
||||
) {
|
||||
FilledTonalButton(onClick = onSelectCustomInjector) {
|
||||
FilledTonalButton(onClick = onSelectCustomTidalApk) {
|
||||
Text(
|
||||
text = customInjector?.version?.toString()
|
||||
text = customTidalApk?.version?.toString()
|
||||
?: stringResource(R.string.componentopts_selected_none)
|
||||
)
|
||||
}
|
||||
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
package com.meowarex.rlmobile.ui.widgets.managerupdate
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import com.meowarex.rlmobile.R
|
||||
import com.meowarex.rlmobile.ui.components.Tag
|
||||
|
||||
data class VersionDelta(
|
||||
val label: String,
|
||||
@DrawableRes val iconRes: Int,
|
||||
val from: String?,
|
||||
val to: String,
|
||||
val tag: String? = null,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun ManagerUpdateDialog(
|
||||
deltas: List<VersionDelta>,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
confirmButton = {
|
||||
FilledTonalButton(
|
||||
onClick = onDismiss,
|
||||
colors = ButtonDefaults.filledTonalButtonColors(
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
contentColor = MaterialTheme.colorScheme.onPrimary,
|
||||
),
|
||||
) {
|
||||
Text(stringResource(R.string.action_continue))
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.manager_update_title),
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.manager_update_subtitle),
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
deltas.forEach { delta ->
|
||||
DeltaCard(delta = delta)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_update),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(36.dp),
|
||||
)
|
||||
},
|
||||
properties = DialogProperties(
|
||||
dismissOnBackPress = true,
|
||||
dismissOnClickOutside = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DeltaCard(delta: VersionDelta) {
|
||||
val changed = delta.from != null && delta.from != delta.to
|
||||
val subtitle = when {
|
||||
delta.from == null || delta.from == delta.to -> delta.to
|
||||
else -> "${delta.from} → ${delta.to}"
|
||||
}
|
||||
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(14.dp),
|
||||
modifier = Modifier.padding(horizontal = 14.dp, vertical = 12.dp),
|
||||
) {
|
||||
DeltaIcon(delta)
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = delta.label,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
Text(
|
||||
text = subtitle,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = if (changed) MaterialTheme.colorScheme.primary
|
||||
else LocalContentColor.current.copy(alpha = 0.65f),
|
||||
fontWeight = if (changed) FontWeight.SemiBold else FontWeight.Normal,
|
||||
)
|
||||
}
|
||||
delta.tag?.let { Tag(text = it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DeltaIcon(delta: VersionDelta) {
|
||||
Surface(
|
||||
shape = CircleShape,
|
||||
color = MaterialTheme.colorScheme.secondaryContainer,
|
||||
modifier = Modifier.size(40.dp),
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
|
||||
Icon(
|
||||
painter = painterResource(delta.iconRes),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
modifier = Modifier.size(22.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M19,3L9,5v9.85c-0.59,-0.34 -1.27,-0.55 -2,-0.55c-2.21,0 -4,1.79 -4,4s1.79,4 4,4s4,-1.79 4,-4V7.36l8,-1.6v6.49c-0.59,-0.34 -1.27,-0.55 -2,-0.55c-2.21,0 -4,1.79 -4,4s1.79,4 4,4s4,-1.79 4,-4V3z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M19,9l1.25,-2.75L23,5l-2.75,-1.25L19,1l-1.25,2.75L15,5l2.75,1.25L19,9zm-7.5,0.5L9,4 6.5,9.5 1,12l5.5,2.5L9,20l2.5,-5.5L17,12l-5.5,-2.5zM19,15l-1.25,2.75L15,19l2.75,1.25L19,23l1.25,-2.75L23,19l-2.75,-1.25z" />
|
||||
</vector>
|
||||
@@ -43,10 +43,18 @@
|
||||
<string name="action_open_info">Open Info</string>
|
||||
<string name="action_reset_default">Reset to default</string>
|
||||
|
||||
<string name="intent_reinstall_fail">Failed to automatically reinstall! Please try doing it manually.</string>
|
||||
<string name="intent_reinstall_fail">Failed to automatically repatch! Please try doing it manually.</string>
|
||||
<string name="intent_import_component_success">Successfully imported %s</string>
|
||||
<string name="intent_import_component_failure">Failed to import custom component!</string>
|
||||
|
||||
<string name="manager_update_title">Update Complete</string>
|
||||
<string name="manager_update_subtitle">Manager was successfully updated!</string>
|
||||
<string name="manager_update_row_manager">Manager</string>
|
||||
<string name="manager_update_row_patches">Patches</string>
|
||||
<string name="manager_update_row_tidal">TIDAL</string>
|
||||
<string name="manager_update_tag_complete">Complete</string>
|
||||
<string name="manager_update_tag_available">Available</string>
|
||||
|
||||
<string name="permissions_title">Grant Permissions</string>
|
||||
<string name="permissions_subtitle">Radiant Lyrics Manager requires permissions:</string>
|
||||
<string name="permissions_legend">%s indicates required permissions!</string>
|
||||
@@ -235,10 +243,10 @@
|
||||
>The app name is what\'s displayed in your home launcher. This should be changed on secondary installations for ease of use.</string>
|
||||
<string name="patchopts_debuggable_title">Debuggable</string>
|
||||
<string name="patchopts_debuggable_desc">Enable the debuggable manifest flag. Only use this if you know what you are doing!</string>
|
||||
<string name="patchopts_custom_injector_title">Custom Injector</string>
|
||||
<string name="patchopts_custom_injector_desc">A custom injector build that was imported by Manager.</string>
|
||||
<string name="patchopts_custom_tidal_apk_title">Custom TIDAL APK</string>
|
||||
<string name="patchopts_custom_tidal_apk_desc">Provide your own Stock TIDAL APK to patch instead of the version from Github.</string>
|
||||
<string name="patchopts_custom_patches_title">Custom Patches</string>
|
||||
<string name="patchopts_custom_patches_desc">A custom smali patch bundle that was imported by Manager.</string>
|
||||
<string name="patchopts_custom_patches_desc">Provide your own Patches Zip to use instead of the ones from Github.</string>
|
||||
<string name="patchopts_divider_basic">Basic</string>
|
||||
<string name="patchopts_divider_advanced">Advanced</string>
|
||||
<string name="patchopts_patches_title">Patches</string>
|
||||
|
||||
Reference in New Issue
Block a user