mirror of
https://github.com/meowarex/rl-mobile.git
synced 2026-06-17 21:13:11 +10:00
@@ -142,7 +142,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val targetDir = when (componentType) {
|
val targetDir = when (componentType) {
|
||||||
"injector" -> paths.customInjectorsDir
|
"tidal" -> paths.customTidalApksDir
|
||||||
"patches" -> paths.customPatchesDir
|
"patches" -> paths.customPatchesDir
|
||||||
else -> {
|
else -> {
|
||||||
Log.w(BuildConfig.TAG, "Extra $EXTRA_COMPONENT_TYPE is not a valid value!")
|
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 customComponentsDir = patchingDir.resolve("custom")
|
||||||
|
|
||||||
val customInjectorsDir = customComponentsDir.resolve("injector")
|
val customTidalApksDir = customComponentsDir.resolve("tidal")
|
||||||
|
|
||||||
val customPatchesDir = customComponentsDir.resolve("patches")
|
val customPatchesDir = customComponentsDir.resolve("patches")
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ class PathManager(
|
|||||||
.resolve("patches")
|
.resolve("patches")
|
||||||
.resolve("$version.zip")
|
.resolve("$version.zip")
|
||||||
|
|
||||||
fun customInjectors() = customInjectorsDir.listFiles()?.asList() ?: emptyList()
|
fun customTidalApks() = customTidalApksDir.listFiles()?.asList() ?: emptyList()
|
||||||
|
|
||||||
fun customSmaliPatches() = customPatchesDir.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 showNetworkWarning by booleanPreference("show_network_warning", true)
|
||||||
var showPlayProtectWarning by booleanPreference("show_play_protect_warning", true)
|
var showPlayProtectWarning by booleanPreference("show_play_protect_warning", true)
|
||||||
var autoUpdateCheck by booleanPreference("auto_update_check", 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(),
|
RestoreDownloadsStep(),
|
||||||
|
|
||||||
// Download
|
// Download
|
||||||
DownloadTidalStep(),
|
DownloadTidalStep(options.customTidalApk),
|
||||||
DownloadPatchesStep(options.customPatches),
|
DownloadPatchesStep(options.customPatches),
|
||||||
CopyDependenciesStep(),
|
CopyDependenciesStep(),
|
||||||
|
|
||||||
|
|||||||
+25
-2
@@ -5,12 +5,17 @@ import com.meowarex.rlmobile.R
|
|||||||
import com.meowarex.rlmobile.manager.PathManager
|
import com.meowarex.rlmobile.manager.PathManager
|
||||||
import com.meowarex.rlmobile.patcher.StepRunner
|
import com.meowarex.rlmobile.patcher.StepRunner
|
||||||
import com.meowarex.rlmobile.patcher.steps.base.DownloadStep
|
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.patcher.steps.prepare.FetchInfoStep
|
||||||
|
import com.meowarex.rlmobile.ui.screens.componentopts.PatchComponent
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
|
import java.io.FileNotFoundException
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class DownloadTidalStep : DownloadStep<Int>(), KoinComponent {
|
class DownloadTidalStep(
|
||||||
|
private val custom: PatchComponent?,
|
||||||
|
) : DownloadStep<Int>(), KoinComponent {
|
||||||
private val paths: PathManager by inject()
|
private val paths: PathManager by inject()
|
||||||
|
|
||||||
override val localizedName = R.string.patch_step_dl_tidal_apk
|
override val localizedName = R.string.patch_step_dl_tidal_apk
|
||||||
@@ -22,5 +27,23 @@ class DownloadTidalStep : DownloadStep<Int>(), KoinComponent {
|
|||||||
container.getStep<FetchInfoStep>().data.tidalApkUrl
|
container.getStep<FetchInfoStep>().data.tidalApkUrl
|
||||||
|
|
||||||
override fun getStoredFile(container: StepRunner) =
|
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 class ComponentOptionsParametersProvider : PreviewParameterProvider<ComponentOptionsParameters> {
|
||||||
private val components = persistentListOf(
|
private val components = persistentListOf(
|
||||||
PatchComponent(
|
PatchComponent(
|
||||||
type = PatchComponent.Type.Injector,
|
type = PatchComponent.Type.TidalApk,
|
||||||
version = SemVer(1, 2, 3),
|
version = SemVer(1, 2, 3),
|
||||||
timestamp = Clock.System.now(),
|
timestamp = Clock.System.now(),
|
||||||
),
|
),
|
||||||
PatchComponent(
|
PatchComponent(
|
||||||
type = PatchComponent.Type.Injector,
|
type = PatchComponent.Type.TidalApk,
|
||||||
version = SemVer(2, 3, 1),
|
version = SemVer(2, 3, 1),
|
||||||
timestamp = Clock.System.now() - 10.minutes,
|
timestamp = Clock.System.now() - 10.minutes,
|
||||||
),
|
),
|
||||||
PatchComponent(
|
PatchComponent(
|
||||||
type = PatchComponent.Type.Injector,
|
type = PatchComponent.Type.TidalApk,
|
||||||
version = SemVer(2, 3, 1),
|
version = SemVer(2, 3, 1),
|
||||||
timestamp = Clock.System.now() - 1.days,
|
timestamp = Clock.System.now() - 1.days,
|
||||||
),
|
),
|
||||||
PatchComponent(
|
PatchComponent(
|
||||||
type = PatchComponent.Type.Injector,
|
type = PatchComponent.Type.TidalApk,
|
||||||
version = SemVer(0, 0, 1),
|
version = SemVer(0, 0, 1),
|
||||||
timestamp = Clock.System.now() - 10.hours,
|
timestamp = Clock.System.now() - 10.hours,
|
||||||
),
|
),
|
||||||
PatchComponent(
|
PatchComponent(
|
||||||
type = PatchComponent.Type.Injector,
|
type = PatchComponent.Type.TidalApk,
|
||||||
version = SemVer(3, 0, 2),
|
version = SemVer(3, 0, 2),
|
||||||
timestamp = Clock.System.now() - 7.days,
|
timestamp = Clock.System.now() - 7.days,
|
||||||
),
|
),
|
||||||
@@ -72,7 +72,7 @@ private class ComponentOptionsParametersProvider : PreviewParameterProvider<Comp
|
|||||||
|
|
||||||
override val values = sequenceOf(
|
override val values = sequenceOf(
|
||||||
ComponentOptionsParameters(
|
ComponentOptionsParameters(
|
||||||
componentType = PatchComponent.Type.Injector,
|
componentType = PatchComponent.Type.TidalApk,
|
||||||
components = components,
|
components = components,
|
||||||
selected = null,
|
selected = null,
|
||||||
),
|
),
|
||||||
|
|||||||
+7
-7
@@ -30,8 +30,8 @@ private fun PatchOptionsScreenPreview(
|
|||||||
packageName = parameters.packageName,
|
packageName = parameters.packageName,
|
||||||
packageNameState = parameters.packageNameState,
|
packageNameState = parameters.packageNameState,
|
||||||
setPackageName = {},
|
setPackageName = {},
|
||||||
customInjector = parameters.customInjector,
|
customTidalApk = parameters.customTidalApk,
|
||||||
onSelectCustomInjector = {},
|
onSelectCustomTidalApk = {},
|
||||||
customPatches = parameters.customPatches,
|
customPatches = parameters.customPatches,
|
||||||
onSelectCustomPatches = {},
|
onSelectCustomPatches = {},
|
||||||
enabledPatchCount = KnownPatch.All.size,
|
enabledPatchCount = KnownPatch.All.size,
|
||||||
@@ -51,7 +51,7 @@ private data class PatchOptionsParameters(
|
|||||||
val appNameIsError: Boolean,
|
val appNameIsError: Boolean,
|
||||||
val packageName: String,
|
val packageName: String,
|
||||||
val packageNameState: PackageNameState,
|
val packageNameState: PackageNameState,
|
||||||
val customInjector: PatchComponent?,
|
val customTidalApk: PatchComponent?,
|
||||||
val customPatches: PatchComponent?,
|
val customPatches: PatchComponent?,
|
||||||
val isConfigValid: Boolean,
|
val isConfigValid: Boolean,
|
||||||
)
|
)
|
||||||
@@ -66,7 +66,7 @@ private class PatchOptionsParametersProvider : PreviewParameterProvider<PatchOpt
|
|||||||
appNameIsError = false,
|
appNameIsError = false,
|
||||||
packageName = PatchOptions.Default.packageName,
|
packageName = PatchOptions.Default.packageName,
|
||||||
packageNameState = PackageNameState.Ok,
|
packageNameState = PackageNameState.Ok,
|
||||||
customInjector = null,
|
customTidalApk = null,
|
||||||
customPatches = null,
|
customPatches = null,
|
||||||
isConfigValid = true,
|
isConfigValid = true,
|
||||||
),
|
),
|
||||||
@@ -78,7 +78,7 @@ private class PatchOptionsParametersProvider : PreviewParameterProvider<PatchOpt
|
|||||||
appNameIsError = true,
|
appNameIsError = true,
|
||||||
packageName = "a b",
|
packageName = "a b",
|
||||||
packageNameState = PackageNameState.Invalid,
|
packageNameState = PackageNameState.Invalid,
|
||||||
customInjector = null,
|
customTidalApk = null,
|
||||||
customPatches = null,
|
customPatches = null,
|
||||||
isConfigValid = false,
|
isConfigValid = false,
|
||||||
),
|
),
|
||||||
@@ -90,8 +90,8 @@ private class PatchOptionsParametersProvider : PreviewParameterProvider<PatchOpt
|
|||||||
appNameIsError = false,
|
appNameIsError = false,
|
||||||
packageName = PatchOptions.Default.packageName,
|
packageName = PatchOptions.Default.packageName,
|
||||||
packageNameState = PackageNameState.Taken,
|
packageNameState = PackageNameState.Taken,
|
||||||
customInjector = PatchComponent(
|
customTidalApk = PatchComponent(
|
||||||
type = PatchComponent.Type.Injector,
|
type = PatchComponent.Type.TidalApk,
|
||||||
version = SemVer(1, 2, 3),
|
version = SemVer(1, 2, 3),
|
||||||
timestamp = Clock.System.now(),
|
timestamp = Clock.System.now(),
|
||||||
),
|
),
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@ class ComponentOptionsModel(
|
|||||||
*/
|
*/
|
||||||
suspend fun refreshComponents(type: PatchComponent.Type) {
|
suspend fun refreshComponents(type: PatchComponent.Type) {
|
||||||
val files = when (type) {
|
val files = when (type) {
|
||||||
PatchComponent.Type.Injector -> paths.customInjectors()
|
PatchComponent.Type.TidalApk -> paths.customTidalApks()
|
||||||
PatchComponent.Type.Patches -> paths.customSmaliPatches()
|
PatchComponent.Type.Patches -> paths.customSmaliPatches()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -34,8 +34,8 @@ data class PatchComponent(
|
|||||||
@Parcelize
|
@Parcelize
|
||||||
@Serializable
|
@Serializable
|
||||||
enum class Type : Parcelable {
|
enum class Type : Parcelable {
|
||||||
@SerialName("injector")
|
@SerialName("tidal")
|
||||||
Injector,
|
TidalApk,
|
||||||
|
|
||||||
@SerialName("patches")
|
@SerialName("patches")
|
||||||
Patches,
|
Patches,
|
||||||
@@ -47,11 +47,11 @@ data class PatchComponent(
|
|||||||
*/
|
*/
|
||||||
fun getFile(paths: PathManager): File {
|
fun getFile(paths: PathManager): File {
|
||||||
val dir = when (type) {
|
val dir = when (type) {
|
||||||
Type.Injector -> paths.customInjectorsDir
|
Type.TidalApk -> paths.customTidalApksDir
|
||||||
Type.Patches -> paths.customPatchesDir
|
Type.Patches -> paths.customPatchesDir
|
||||||
}
|
}
|
||||||
val ext = when (type) {
|
val ext = when (type) {
|
||||||
Type.Injector -> "dex"
|
Type.TidalApk -> "apk"
|
||||||
Type.Patches -> "zip"
|
Type.Patches -> "zip"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import cafe.adriel.voyager.core.model.screenModelScope
|
|||||||
import com.github.diamondminer88.zip.ZipReader
|
import com.github.diamondminer88.zip.ZipReader
|
||||||
import com.meowarex.rlmobile.BuildConfig
|
import com.meowarex.rlmobile.BuildConfig
|
||||||
import com.meowarex.rlmobile.R
|
import com.meowarex.rlmobile.R
|
||||||
|
import com.meowarex.rlmobile.manager.PreferencesManager
|
||||||
import com.meowarex.rlmobile.network.models.GithubCommit
|
import com.meowarex.rlmobile.network.models.GithubCommit
|
||||||
import com.meowarex.rlmobile.network.models.RLBuildInfo
|
import com.meowarex.rlmobile.network.models.RLBuildInfo
|
||||||
import com.meowarex.rlmobile.network.services.RadiantLyricsGithubService
|
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.PatchOptions
|
||||||
import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptionsScreen
|
import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptionsScreen
|
||||||
import com.meowarex.rlmobile.ui.util.TidalVersion
|
import com.meowarex.rlmobile.ui.util.TidalVersion
|
||||||
|
import com.meowarex.rlmobile.ui.widgets.managerupdate.VersionDelta
|
||||||
import com.meowarex.rlmobile.util.*
|
import com.meowarex.rlmobile.util.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
@@ -39,11 +41,15 @@ class HomeModel(
|
|||||||
private val application: Application,
|
private val application: Application,
|
||||||
private val github: RadiantLyricsGithubService,
|
private val github: RadiantLyricsGithubService,
|
||||||
private val json: Json,
|
private val json: Json,
|
||||||
|
private val prefs: PreferencesManager,
|
||||||
) : ScreenModel {
|
) : ScreenModel {
|
||||||
|
|
||||||
var state by mutableStateOf<HomeState>(HomeState.Loading)
|
var state by mutableStateOf<HomeState>(HomeState.Loading)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
var managerUpdateDeltas by mutableStateOf<List<VersionDelta>?>(null)
|
||||||
|
private set
|
||||||
|
|
||||||
val commits = Pager(PagingConfig(pageSize = 30)) {
|
val commits = Pager(PagingConfig(pageSize = 30)) {
|
||||||
CommitsPagingSource(github)
|
CommitsPagingSource(github)
|
||||||
}.flow.cachedIn(screenModelScope)
|
}.flow.cachedIn(screenModelScope)
|
||||||
@@ -51,10 +57,103 @@ class HomeModel(
|
|||||||
private val refreshingLock = Mutex()
|
private val refreshingLock = Mutex()
|
||||||
private var remoteDataJson: RLBuildInfo? = null
|
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 {
|
init {
|
||||||
refresh()
|
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 {
|
fun refresh(delay: Boolean = false) = screenModelScope.launchIO {
|
||||||
if (refreshingLock.isLocked) return@launchIO
|
if (refreshingLock.isLocked) return@launchIO
|
||||||
if (delay) {
|
if (delay) {
|
||||||
@@ -75,6 +174,7 @@ class HomeModel(
|
|||||||
install = install,
|
install = install,
|
||||||
latestTidalVersionCode = latest,
|
latestTidalVersionCode = latest,
|
||||||
)
|
)
|
||||||
|
maybeCheckManagerUpdate(pkg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,7 +189,7 @@ class HomeModel(
|
|||||||
openAppInfo(current.packageName)
|
openAppInfo(current.packageName)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createReinstallScreen(): PatchOptionsScreen? {
|
fun createRepatchScreen(): PatchOptionsScreen? {
|
||||||
val current = (state as? HomeState.Loaded)?.install ?: return null
|
val current = (state as? HomeState.Loaded)?.install ?: return null
|
||||||
return createPrefilledPatchOptsScreen(current.packageName)
|
return createPrefilledPatchOptsScreen(current.packageName)
|
||||||
}
|
}
|
||||||
@@ -112,20 +212,21 @@ class HomeModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createPrefilledPatchOptsScreen(packageName: String): PatchOptionsScreen {
|
fun createPrefilledPatchOptsScreen(packageName: String): PatchOptionsScreen {
|
||||||
val metadata = try {
|
val patchOptions = loadInstallMetadata(packageName)?.options
|
||||||
val applicationInfo = application.packageManager.getApplicationInfo(packageName, 0)
|
?: PatchOptions.Default.copy(packageName = packageName)
|
||||||
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)
|
|
||||||
return PatchOptionsScreen(prefilledOptions = patchOptions)
|
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
|
private fun fetchInstalled(): PackageInfo? = application.packageManager
|
||||||
.getInstalledPackages(PackageManager.GET_META_DATA)
|
.getInstalledPackages(PackageManager.GET_META_DATA)
|
||||||
.firstOrNull { it.applicationInfo?.metaData?.containsKey("isRadiantLyrics") == true }
|
.firstOrNull { it.applicationInfo?.metaData?.containsKey("isRadiantLyrics") == true }
|
||||||
@@ -138,7 +239,8 @@ class HomeModel(
|
|||||||
return InstallData(
|
return InstallData(
|
||||||
name = pm.getApplicationLabel(info).toString(),
|
name = pm.getApplicationLabel(info).toString(),
|
||||||
packageName = packageName,
|
packageName = packageName,
|
||||||
isUpToDate = isInstallationUpToDate(this),
|
tidalUpToDate = isTidalUpToDate(this),
|
||||||
|
patchesUpToDate = isPatchesUpToDate(this),
|
||||||
icon = pm.getApplicationIcon(info).toBitmap().asImageBitmap().let(::BitmapPainter),
|
icon = pm.getApplicationIcon(info).toBitmap().asImageBitmap().let(::BitmapPainter),
|
||||||
version = TidalVersion.Existing(
|
version = TidalVersion.Existing(
|
||||||
type = TidalVersion.parseVersionType(versionCode),
|
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
|
val remote = remoteDataJson ?: return null
|
||||||
@Suppress("DEPRECATION") val versionCode = pkg.versionCode
|
@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 apkPath = pkg.applicationInfo?.publicSourceDir ?: return false
|
||||||
val installMetadata = try {
|
val installMetadata = try {
|
||||||
val mf = ZipReader(apkPath).use { it.openEntry("rlmobile.json")?.read() } ?: return false
|
val mf = ZipReader(apkPath).use { it.openEntry("rlmobile.json")?.read() } ?: return false
|
||||||
@@ -182,8 +287,6 @@ class HomeModel(
|
|||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (installMetadata.options.customPatches != null) return true
|
|
||||||
return remote.patchesVersion == installMetadata.patchesVersion
|
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.logs.LogsListScreen
|
||||||
import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptionsScreen
|
import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptionsScreen
|
||||||
import com.meowarex.rlmobile.ui.screens.settings.SettingsScreen
|
import com.meowarex.rlmobile.ui.screens.settings.SettingsScreen
|
||||||
|
import com.meowarex.rlmobile.ui.widgets.managerupdate.ManagerUpdateDialog
|
||||||
import com.meowarex.rlmobile.util.*
|
import com.meowarex.rlmobile.util.*
|
||||||
import kotlinx.parcelize.IgnoredOnParcel
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
@@ -106,9 +107,9 @@ class HomeScreen : Screen, Parcelable {
|
|||||||
state = state,
|
state = state,
|
||||||
commits = model.commits,
|
commits = model.commits,
|
||||||
onInstall = { navigator.pushOnce(PatchOptionsScreen()) },
|
onInstall = { navigator.pushOnce(PatchOptionsScreen()) },
|
||||||
onReinstall = {
|
onRepatch = {
|
||||||
scope.launchIO {
|
scope.launchIO {
|
||||||
val screen = model.createReinstallScreen() ?: return@launchIO
|
val screen = model.createRepatchScreen() ?: return@launchIO
|
||||||
mainThread { navigator.push(screen) }
|
mainThread { navigator.push(screen) }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -116,6 +117,13 @@ class HomeScreen : Screen, Parcelable {
|
|||||||
onInfo = model::openCurrentAppInfo,
|
onInfo = model::openCurrentAppInfo,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model.managerUpdateDeltas?.let { deltas ->
|
||||||
|
ManagerUpdateDialog(
|
||||||
|
deltas = deltas,
|
||||||
|
onDismiss = model::dismissManagerUpdate,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,12 +134,13 @@ private fun ColumnScope.HomeContent(
|
|||||||
state: HomeState.Loaded,
|
state: HomeState.Loaded,
|
||||||
commits: kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<com.meowarex.rlmobile.network.models.GithubCommit>>,
|
commits: kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<com.meowarex.rlmobile.network.models.GithubCommit>>,
|
||||||
onInstall: () -> Unit,
|
onInstall: () -> Unit,
|
||||||
onReinstall: () -> Unit,
|
onRepatch: () -> Unit,
|
||||||
onLaunch: () -> Unit,
|
onLaunch: () -> Unit,
|
||||||
onInfo: () -> Unit,
|
onInfo: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val install = state.install
|
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 latestVersionName = state.latestTidalVersionCode?.let { "build $it" }
|
||||||
|
|
||||||
val fallbackPainter = if (install?.icon == null) {
|
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(
|
Button(
|
||||||
onClick = if (install == null) onInstall else onReinstall,
|
onClick = if (install == null) onInstall else onRepatch,
|
||||||
enabled = state.latestTidalVersionCode != null,
|
enabled = state.latestTidalVersionCode != null,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
@@ -190,7 +208,7 @@ private fun ColumnScope.HomeContent(
|
|||||||
state.latestTidalVersionCode == null -> "Loading…"
|
state.latestTidalVersionCode == null -> "Loading…"
|
||||||
install == null -> "Install"
|
install == null -> "Install"
|
||||||
install.isUpToDate == false -> "Update"
|
install.isUpToDate == false -> "Update"
|
||||||
else -> "Reinstall"
|
else -> "Repatch"
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
text = label,
|
text = label,
|
||||||
@@ -222,5 +240,20 @@ private fun ColumnScope.HomeContent(
|
|||||||
|
|
||||||
ElevatedCard(modifier = Modifier.fillMaxSize()) {
|
ElevatedCard(modifier = Modifier.fillMaxSize()) {
|
||||||
CommitList(commits = commits.collectAsLazyPagingItems())
|
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 packageName: String,
|
||||||
val version: TidalVersion,
|
val version: TidalVersion,
|
||||||
val icon: BitmapPainter,
|
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,
|
val debuggable: Boolean,
|
||||||
|
|
||||||
/**
|
val customTidalApk: PatchComponent? = null,
|
||||||
* A custom build of injector that was used rather than the latest.
|
|
||||||
*/
|
|
||||||
val customInjector: PatchComponent? = null,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A custom smali patches bundle that was used rather than the latest.
|
* A custom smali patches bundle that was used rather than the latest.
|
||||||
@@ -42,9 +39,9 @@ data class PatchOptions(
|
|||||||
appName = "TIDAL",
|
appName = "TIDAL",
|
||||||
packageName = "com.aspiro.tidal",
|
packageName = "com.aspiro.tidal",
|
||||||
debuggable = false,
|
debuggable = false,
|
||||||
customInjector = null,
|
customTidalApk = null,
|
||||||
customPatches = null,
|
customPatches = null,
|
||||||
disabledPatches = emptySet(),
|
disabledPatches = (KnownPatch.DebugMenuUnlock.fileNames + KnownPatch.EnableLegacyUi.fileNames).toSet(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-6
@@ -81,16 +81,16 @@ class PatchOptionsModel(
|
|||||||
val enabledPatchCount: Int
|
val enabledPatchCount: Int
|
||||||
get() = KnownPatch.All.count { isPatchEnabled(it) }
|
get() = KnownPatch.All.count { isPatchEnabled(it) }
|
||||||
|
|
||||||
var customInjector by mutableStateOf<PatchComponent?>(null)
|
var customTidalApk by mutableStateOf<PatchComponent?>(null)
|
||||||
private set
|
private set
|
||||||
var customPatches by mutableStateOf<PatchComponent?>(null)
|
var customPatches by mutableStateOf<PatchComponent?>(null)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
fun selectCustomInjector(navigator: Navigator) = screenModelScope.launch {
|
fun selectCustomTidalApk(navigator: Navigator) = screenModelScope.launch {
|
||||||
customInjector = navigator.pushForResult(
|
customTidalApk = navigator.pushForResult(
|
||||||
ComponentOptionsScreen(
|
ComponentOptionsScreen(
|
||||||
default = customInjector,
|
default = customTidalApk,
|
||||||
componentType = PatchComponent.Type.Injector,
|
componentType = PatchComponent.Type.TidalApk,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -120,7 +120,7 @@ class PatchOptionsModel(
|
|||||||
appName = appName,
|
appName = appName,
|
||||||
packageName = packageName,
|
packageName = packageName,
|
||||||
debuggable = debuggable,
|
debuggable = debuggable,
|
||||||
customInjector = customInjector,
|
customTidalApk = customTidalApk,
|
||||||
customPatches = customPatches,
|
customPatches = customPatches,
|
||||||
disabledPatches = disabledPatches,
|
disabledPatches = disabledPatches,
|
||||||
)
|
)
|
||||||
|
|||||||
+10
-10
@@ -55,9 +55,9 @@ class PatchOptionsScreen(
|
|||||||
packageNameState = model.packageNameState,
|
packageNameState = model.packageNameState,
|
||||||
setPackageName = model::changePackageName,
|
setPackageName = model::changePackageName,
|
||||||
|
|
||||||
customInjector = model.customInjector,
|
customTidalApk = model.customTidalApk,
|
||||||
customPatches = model.customPatches,
|
customPatches = model.customPatches,
|
||||||
onSelectCustomInjector = { model.selectCustomInjector(navigator) },
|
onSelectCustomTidalApk = { model.selectCustomTidalApk(navigator) },
|
||||||
onSelectCustomPatches = { model.selectCustomPatches(navigator) },
|
onSelectCustomPatches = { model.selectCustomPatches(navigator) },
|
||||||
|
|
||||||
enabledPatchCount = model.enabledPatchCount,
|
enabledPatchCount = model.enabledPatchCount,
|
||||||
@@ -88,8 +88,8 @@ fun PatchOptionsScreenContent(
|
|||||||
packageNameState: PackageNameState,
|
packageNameState: PackageNameState,
|
||||||
setPackageName: (String) -> Unit,
|
setPackageName: (String) -> Unit,
|
||||||
|
|
||||||
customInjector: PatchComponent?,
|
customTidalApk: PatchComponent?,
|
||||||
onSelectCustomInjector: () -> Unit,
|
onSelectCustomTidalApk: () -> Unit,
|
||||||
customPatches: PatchComponent?,
|
customPatches: PatchComponent?,
|
||||||
onSelectCustomPatches: () -> Unit,
|
onSelectCustomPatches: () -> Unit,
|
||||||
|
|
||||||
@@ -180,14 +180,14 @@ fun PatchOptionsScreenContent(
|
|||||||
)
|
)
|
||||||
|
|
||||||
IconPatchOption(
|
IconPatchOption(
|
||||||
icon = painterResource(R.drawable.ic_extension),
|
icon = painterResource(R.drawable.ic_music_note),
|
||||||
name = stringResource(R.string.patchopts_custom_injector_title),
|
name = stringResource(R.string.patchopts_custom_tidal_apk_title),
|
||||||
description = stringResource(R.string.patchopts_custom_injector_desc),
|
description = stringResource(R.string.patchopts_custom_tidal_apk_desc),
|
||||||
modifier = Modifier.clickable(onClick = onSelectCustomInjector),
|
modifier = Modifier.clickable(onClick = onSelectCustomTidalApk),
|
||||||
) {
|
) {
|
||||||
FilledTonalButton(onClick = onSelectCustomInjector) {
|
FilledTonalButton(onClick = onSelectCustomTidalApk) {
|
||||||
Text(
|
Text(
|
||||||
text = customInjector?.version?.toString()
|
text = customTidalApk?.version?.toString()
|
||||||
?: stringResource(R.string.componentopts_selected_none)
|
?: 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_open_info">Open Info</string>
|
||||||
<string name="action_reset_default">Reset to default</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_success">Successfully imported %s</string>
|
||||||
<string name="intent_import_component_failure">Failed to import custom component!</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_title">Grant Permissions</string>
|
||||||
<string name="permissions_subtitle">Radiant Lyrics Manager requires permissions:</string>
|
<string name="permissions_subtitle">Radiant Lyrics Manager requires permissions:</string>
|
||||||
<string name="permissions_legend">%s indicates required 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>
|
>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_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_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_tidal_apk_title">Custom TIDAL APK</string>
|
||||||
<string name="patchopts_custom_injector_desc">A custom injector build that was imported by Manager.</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_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_basic">Basic</string>
|
||||||
<string name="patchopts_divider_advanced">Advanced</string>
|
<string name="patchopts_divider_advanced">Advanced</string>
|
||||||
<string name="patchopts_patches_title">Patches</string>
|
<string name="patchopts_patches_title">Patches</string>
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"tidalVersionCode": 9089,
|
"tidalVersionCode": 9089,
|
||||||
"tidalApkUrl": "https://github.com/meowarex/rl-mobile/releases/download/latest/tidal-stock.apk",
|
"tidalApkUrl": "https://github.com/meowarex/rl-mobile/releases/download/latest/tidal-stock.apk",
|
||||||
"patchesVersion": "0.5.0"
|
"patchesVersion": "0.6.0"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user