mirror of
https://github.com/meowarex/rl-mobile.git
synced 2026-08-27 06:27:46 +10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
266cd184dd | ||
|
|
04ea66acdb | ||
|
|
22526f4e0b | ||
|
|
5a3ad2df7a | ||
|
|
fa99d10134 | ||
|
|
4e8af5960c | ||
|
|
09f6e8d34f | ||
|
|
160be11ac4 | ||
|
|
4486e52117 | ||
|
|
bafd40a20c | ||
|
|
c89827c7b2 | ||
|
|
90b49cc571 | ||
|
|
8e86c0d2e1 | ||
|
|
8ac9fb0db5 | ||
|
|
6ad6cb2fe0 | ||
|
|
a9611a1667 | ||
|
|
276dd0be2e | ||
|
|
3758ba19f3 | ||
|
|
75450aad60 | ||
|
|
3788eb2008 | ||
|
|
95be099821 | ||
|
|
7a55787894 | ||
|
|
bd09c2798e | ||
|
|
b0b614dc6f | ||
|
|
6a7d5fe90f | ||
|
|
4791e69bec | ||
|
|
7f3618aeae | ||
|
|
ea9c6458c2 | ||
|
|
bdbcc4fc62 | ||
|
|
3b893c12ff | ||
|
|
9d60e3dc80 | ||
|
|
36382437f1 | ||
|
|
5dc9188772 | ||
|
|
7823a54d11 | ||
|
|
4d5fea843e | ||
|
|
d6bc889387 | ||
|
|
8501ccb0a9 | ||
|
|
ad6ebac6ce | ||
|
|
320148e774 | ||
|
|
d8e119eb4b | ||
|
|
10e5dcf365 | ||
|
|
1c65c5669a | ||
|
|
961291166c | ||
|
|
25852aaea1 | ||
|
|
c6b18d1aa9 | ||
|
|
ee8b25330d | ||
|
|
193d5ad90d | ||
|
|
942742e86e | ||
|
|
0b039110c9 | ||
|
|
045bbbab9a | ||
|
|
932f2a85dc | ||
|
|
fc002615f4 | ||
|
|
c89dd54fa6 | ||
|
|
53a4341d6b | ||
|
|
c81d2edba3 | ||
|
|
9180129afc | ||
|
|
d9de3207f5 | ||
|
|
59fe232ae4 | ||
|
|
e105a3eb35 | ||
|
|
e7b69a8deb | ||
|
|
324a6eb6c8 | ||
|
|
c5962ad1a8 | ||
|
|
77ab041b97 |
@@ -48,6 +48,8 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
mv ./dist/app-release.apk ./dist/rl-manager.apk
|
mv ./dist/app-release.apk ./dist/rl-manager.apk
|
||||||
cp patches/data.json ./dist/data.json
|
cp patches/data.json ./dist/data.json
|
||||||
|
# Point each release's data.json at its own assets so historical releases stay self-contained.
|
||||||
|
sed -i "s|releases/download/latest/|releases/download/v${{ needs.Version.outputs.version }}/|g" ./dist/data.json
|
||||||
cd patches && zip -r ../dist/patches.zip . -x "data.json" && cd ..
|
cd patches && zip -r ../dist/patches.zip . -x "data.json" && cd ..
|
||||||
|
|
||||||
tidal_src=$(find tidal-apk -maxdepth 1 \( -name "*.apk" -o -name "*.apkm" \) | head -1)
|
tidal_src=$(find tidal-apk -maxdepth 1 \( -name "*.apk" -o -name "*.apkm" \) | head -1)
|
||||||
@@ -58,7 +60,7 @@ jobs:
|
|||||||
if [[ "$tidal_src" == *.apkm ]]; then
|
if [[ "$tidal_src" == *.apkm ]]; then
|
||||||
echo "Merging splits from $tidal_src via APKEditor"
|
echo "Merging splits from $tidal_src via APKEditor"
|
||||||
curl --fail --location --retry 3 --retry-delay 2 -sSo /tmp/APKEditor.jar \
|
curl --fail --location --retry 3 --retry-delay 2 -sSo /tmp/APKEditor.jar \
|
||||||
https://github.com/REAndroid/APKEditor/releases/download/V1.4.3/APKEditor-1.4.3.jar
|
https://github.com/REAndroid/APKEditor/releases/download/V1.4.9/APKEditor-1.4.9.jar
|
||||||
java -jar /tmp/APKEditor.jar m -i "$tidal_src" -o ./dist/tidal-stock.apk
|
java -jar /tmp/APKEditor.jar m -i "$tidal_src" -o ./dist/tidal-stock.apk
|
||||||
echo "Merged tidal-stock.apk:"
|
echo "Merged tidal-stock.apk:"
|
||||||
ls -la ./dist/tidal-stock.apk
|
ls -la ./dist/tidal-stock.apk
|
||||||
@@ -67,10 +69,10 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Publish release
|
- name: Publish release
|
||||||
uses: marvinpinto/action-automatic-releases@latest
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
tag_name: v${{ needs.Version.outputs.version }}
|
||||||
automatic_release_tag: latest
|
name: v${{ needs.Version.outputs.version }}
|
||||||
prerelease: false
|
prerelease: false
|
||||||
title: v${{ needs.Version.outputs.version }}
|
make_latest: "true"
|
||||||
files: ./dist/**
|
files: ./dist/**
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -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!")
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import com.meowarex.rlmobile.ui.screens.about.AboutModel
|
|||||||
import com.meowarex.rlmobile.ui.screens.componentopts.ComponentOptionsModel
|
import com.meowarex.rlmobile.ui.screens.componentopts.ComponentOptionsModel
|
||||||
import com.meowarex.rlmobile.ui.screens.home.HomeModel
|
import com.meowarex.rlmobile.ui.screens.home.HomeModel
|
||||||
import com.meowarex.rlmobile.ui.screens.log.LogScreenModel
|
import com.meowarex.rlmobile.ui.screens.log.LogScreenModel
|
||||||
import com.meowarex.rlmobile.ui.screens.logs.LogsListScreenModel
|
|
||||||
import com.meowarex.rlmobile.ui.screens.patching.PatchingScreenModel
|
import com.meowarex.rlmobile.ui.screens.patching.PatchingScreenModel
|
||||||
import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptionsModel
|
import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptionsModel
|
||||||
import com.meowarex.rlmobile.ui.screens.permissions.PermissionsModel
|
import com.meowarex.rlmobile.ui.screens.permissions.PermissionsModel
|
||||||
@@ -64,7 +63,6 @@ class ManagerApplication : Application() {
|
|||||||
factoryOf(::PatchOptionsModel)
|
factoryOf(::PatchOptionsModel)
|
||||||
factoryOf(::ComponentOptionsModel)
|
factoryOf(::ComponentOptionsModel)
|
||||||
factoryOf(::LogScreenModel)
|
factoryOf(::LogScreenModel)
|
||||||
factoryOf(::LogsListScreenModel)
|
|
||||||
factoryOf(::PermissionsModel)
|
factoryOf(::PermissionsModel)
|
||||||
viewModelOf(::UpdaterViewModel)
|
viewModelOf(::UpdaterViewModel)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptions
|
|||||||
import com.meowarex.rlmobile.util.*
|
import com.meowarex.rlmobile.util.*
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.decodeFromStream
|
|
||||||
import java.io.IOException
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@@ -21,52 +19,19 @@ import kotlin.time.Duration
|
|||||||
import kotlin.time.Instant
|
import kotlin.time.Instant
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Central manager for storing all attempted installations and
|
* In-memory store for the most recent install attempts
|
||||||
* their associated logs/crashes (not including manager crashes themselves).
|
|
||||||
*/
|
*/
|
||||||
class InstallLogManager(
|
class InstallLogManager(
|
||||||
private val application: Application,
|
private val application: Application,
|
||||||
private val prefs: PreferencesManager,
|
private val prefs: PreferencesManager,
|
||||||
private val json: Json,
|
@Suppress("unused") private val json: Json,
|
||||||
) {
|
) {
|
||||||
val logsDir = application.filesDir.resolve("install-logs").apply { mkdir() }
|
private val entries = linkedMapOf<String, InstallLogData>()
|
||||||
|
|
||||||
/**
|
fun fetchInstallData(id: String): InstallLogData? = synchronized(entries) {
|
||||||
* Lists all the install data entries that exist on disk, sorted decreasing by
|
entries[id]
|
||||||
* the file creation date.
|
|
||||||
* @return List of installation ids, most recent installation first.
|
|
||||||
*/
|
|
||||||
fun fetchInstallDataEntries(): List<String> {
|
|
||||||
val files = logsDir.listFiles { it.extension == "json" } ?: emptyArray()
|
|
||||||
|
|
||||||
return files
|
|
||||||
.sortedByDescending { it.lastModified() }
|
|
||||||
.map { it.nameWithoutExtension }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the install log from disk, if it exists.
|
|
||||||
*/
|
|
||||||
fun fetchInstallData(id: String): InstallLogData? {
|
|
||||||
val path = logsDir.resolve("$id.json")
|
|
||||||
if (!path.exists()) return null
|
|
||||||
|
|
||||||
return try {
|
|
||||||
json.decodeFromStream(path.inputStream())
|
|
||||||
} catch (t: Throwable) {
|
|
||||||
Log.e(BuildConfig.TAG, "Failed to open install log $id", t)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun deleteAllEntries() {
|
|
||||||
logsDir.deleteRecursively()
|
|
||||||
logsDir.mkdir()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes an install log entry to disk.
|
|
||||||
*/
|
|
||||||
suspend fun storeInstallData(
|
suspend fun storeInstallData(
|
||||||
id: String,
|
id: String,
|
||||||
installDate: Instant,
|
installDate: Instant,
|
||||||
@@ -75,8 +40,6 @@ class InstallLogManager(
|
|||||||
log: String,
|
log: String,
|
||||||
error: Throwable?,
|
error: Throwable?,
|
||||||
) {
|
) {
|
||||||
val path = logsDir.resolve("$id.json")
|
|
||||||
|
|
||||||
val data = InstallLogData(
|
val data = InstallLogData(
|
||||||
id = id,
|
id = id,
|
||||||
installDate = installDate,
|
installDate = installDate,
|
||||||
@@ -87,16 +50,16 @@ class InstallLogManager(
|
|||||||
errorStacktrace = error?.let { Log.getStackTraceString(it).trimEnd() },
|
errorStacktrace = error?.let { Log.getStackTraceString(it).trimEnd() },
|
||||||
)
|
)
|
||||||
|
|
||||||
try {
|
synchronized(entries) {
|
||||||
path.writeText(json.encodeToString(data))
|
entries[id] = data
|
||||||
} catch (e: IOException) {
|
// Keep only the most recent few in memory.
|
||||||
Log.e(BuildConfig.TAG, "Failed to write log to disk", e)
|
while (entries.size > MAX_ENTRIES) {
|
||||||
|
val oldest = entries.keys.iterator().next()
|
||||||
|
entries.remove(oldest)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a list of details about the current installation environment.
|
|
||||||
*/
|
|
||||||
@Suppress("KotlinConstantConditions", "SimplifyBooleanWithConstants")
|
@Suppress("KotlinConstantConditions", "SimplifyBooleanWithConstants")
|
||||||
@SuppressLint("UsableSpace")
|
@SuppressLint("UsableSpace")
|
||||||
suspend fun getEnvironmentInfo(): String {
|
suspend fun getEnvironmentInfo(): String {
|
||||||
@@ -139,6 +102,10 @@ class InstallLogManager(
|
|||||||
SOC: $soc
|
SOC: $soc
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val MAX_ENTRIES = 8
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ class PreferencesManager(preferences: SharedPreferences) : BasePreferenceManager
|
|||||||
var devMode by booleanPreference("dev_mode", false)
|
var devMode by booleanPreference("dev_mode", false)
|
||||||
var installer by enumPreference<InstallerSetting>("installer", InstallerSetting.PackageInstaller)
|
var installer by enumPreference<InstallerSetting>("installer", InstallerSetting.PackageInstaller)
|
||||||
var keepPatchedApks by booleanPreference("keep_patched_apks", false)
|
var keepPatchedApks by booleanPreference("keep_patched_apks", false)
|
||||||
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+100
-15
@@ -8,7 +8,9 @@ import com.meowarex.rlmobile.patcher.steps.base.IDexProvider
|
|||||||
import com.meowarex.rlmobile.patcher.steps.base.Step
|
import com.meowarex.rlmobile.patcher.steps.base.Step
|
||||||
import com.meowarex.rlmobile.patcher.steps.download.CopyDependenciesStep
|
import com.meowarex.rlmobile.patcher.steps.download.CopyDependenciesStep
|
||||||
import com.meowarex.rlmobile.patcher.steps.download.DownloadPatchesStep
|
import com.meowarex.rlmobile.patcher.steps.download.DownloadPatchesStep
|
||||||
|
import com.meowarex.rlmobile.ui.screens.patchopts.PatchManifest
|
||||||
import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptions
|
import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptions
|
||||||
|
import com.meowarex.rlmobile.ui.screens.patchopts.builtinPatchSpecs
|
||||||
import com.android.tools.smali.baksmali.Baksmali
|
import com.android.tools.smali.baksmali.Baksmali
|
||||||
import com.android.tools.smali.baksmali.BaksmaliOptions
|
import com.android.tools.smali.baksmali.BaksmaliOptions
|
||||||
import com.android.tools.smali.dexlib2.Opcodes
|
import com.android.tools.smali.dexlib2.Opcodes
|
||||||
@@ -19,6 +21,7 @@ import com.github.diamondminer88.zip.ZipReader
|
|||||||
import com.github.difflib.DiffUtils
|
import com.github.difflib.DiffUtils
|
||||||
import com.github.difflib.UnifiedDiffUtils
|
import com.github.difflib.UnifiedDiffUtils
|
||||||
import com.github.difflib.patch.Patch
|
import com.github.difflib.patch.Patch
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
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.*
|
import java.io.*
|
||||||
@@ -27,6 +30,7 @@ class SmaliPatchStep(
|
|||||||
private val options: PatchOptions,
|
private val options: PatchOptions,
|
||||||
) : Step(), IDexProvider, KoinComponent {
|
) : Step(), IDexProvider, KoinComponent {
|
||||||
private val paths: PathManager by inject()
|
private val paths: PathManager by inject()
|
||||||
|
private val json: Json by inject()
|
||||||
|
|
||||||
override val group = StepGroup.Patch
|
override val group = StepGroup.Patch
|
||||||
override val localizedName = R.string.patch_step_patch_smali
|
override val localizedName = R.string.patch_step_patch_smali
|
||||||
@@ -42,6 +46,29 @@ class SmaliPatchStep(
|
|||||||
val patches = mutableListOf<LoadedPatch>()
|
val patches = mutableListOf<LoadedPatch>()
|
||||||
val localsBumps = mutableMapOf<Pair<String, String>, Int>()
|
val localsBumps = mutableMapOf<Pair<String, String>, Int>()
|
||||||
|
|
||||||
|
// The patch list/options metadata lives in the zip's manifest.json
|
||||||
|
val manifestSpecs = try {
|
||||||
|
ZipReader(patchesZip).use { it.openEntry("manifest.json")?.read() }
|
||||||
|
?.let { json.decodeFromString(PatchManifest.serializer(), it.decodeToString()).patches }
|
||||||
|
?.takeIf { it.isNotEmpty() }
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
container.log("Failed to parse manifest.json (${t.message}); using built-in patch specs")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
val specs = manifestSpecs ?: builtinPatchSpecs { "" }
|
||||||
|
container.log(
|
||||||
|
"Loaded ${specs.size} patch specs from " +
|
||||||
|
if (manifestSpecs != null) "manifest.json" else "built-in list"
|
||||||
|
)
|
||||||
|
|
||||||
|
val disabledFiles = options.disabledPatchFiles(specs)
|
||||||
|
val knownExtensionFiles = options.knownExtensionFiles(specs)
|
||||||
|
val enabledExtensionFiles = options.enabledExtensionFiles(specs)
|
||||||
|
val substitutions = options.smaliSubstitutions(specs)
|
||||||
|
if (substitutions.isNotEmpty()) {
|
||||||
|
container.log("Patch option substitutions: ${substitutions.entries.joinToString { "${it.key}=${it.value}" }}")
|
||||||
|
}
|
||||||
|
|
||||||
// Load and parse all the patches from the smali archive.
|
// Load and parse all the patches from the smali archive.
|
||||||
container.log("Loading patches from smali patch archive: ${patchesZip.absolutePath}")
|
container.log("Loading patches from smali patch archive: ${patchesZip.absolutePath}")
|
||||||
smaliDir.mkdirs()
|
smaliDir.mkdirs()
|
||||||
@@ -53,6 +80,11 @@ class SmaliPatchStep(
|
|||||||
|
|
||||||
if (patchFile.endsWith(".smali") && patchFile.startsWith("extension/")) {
|
if (patchFile.endsWith(".smali") && patchFile.startsWith("extension/")) {
|
||||||
val relative = patchFile.removePrefix("extension/")
|
val relative = patchFile.removePrefix("extension/")
|
||||||
|
// Only bundle helper smali patch/variant/sub-option is enabled
|
||||||
|
if (relative in knownExtensionFiles && relative !in enabledExtensionFiles) {
|
||||||
|
container.log("Skipping disabled extension smali: $relative")
|
||||||
|
continue
|
||||||
|
}
|
||||||
val out = smaliDir.resolve(relative)
|
val out = smaliDir.resolve(relative)
|
||||||
// Guard against zip-slip: a crafted entry could otherwise escape smaliDir.
|
// Guard against zip-slip: a crafted entry could otherwise escape smaliDir.
|
||||||
val baseCanonical = smaliDir.canonicalPath + File.separator
|
val baseCanonical = smaliDir.canonicalPath + File.separator
|
||||||
@@ -71,16 +103,39 @@ class SmaliPatchStep(
|
|||||||
if (!patchFile.endsWith(".patch")) continue
|
if (!patchFile.endsWith(".patch")) continue
|
||||||
|
|
||||||
val basename = patchFile.substringAfterLast('/')
|
val basename = patchFile.substringAfterLast('/')
|
||||||
if (basename in options.disabledPatches) {
|
if (basename in disabledFiles) {
|
||||||
container.log("Skipping disabled patch $patchFile")
|
container.log("Skipping disabled patch $patchFile")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
val lines = zip.openEntry(patchFile)!!.read()
|
var patchText = zip.openEntry(patchFile)!!.read()
|
||||||
.decodeToString()
|
.decodeToString()
|
||||||
.replace("\r\n", "\n") // Replace CRLF endings with LF
|
.replace("\r\n", "\n") // Replace CRLF endings with LF
|
||||||
.trimEnd { it == '\n' } // Remove trailing new lines
|
.trimEnd { it == '\n' } // Remove trailing new lines
|
||||||
.split('\n')
|
|
||||||
|
// Bake advanced option values into the patch
|
||||||
|
for ((token, value) in substitutions) {
|
||||||
|
if (patchText.contains(token)) {
|
||||||
|
patchText = patchText.replace(token, value)
|
||||||
|
container.log("Applied substitution $token -> $value in $patchFile")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail fast
|
||||||
|
UNRESOLVED_TOKEN.find(patchText)?.let { match ->
|
||||||
|
throw Error("Unresolved option placeholder ${match.value} in $patchFile")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip a patch that references a helper class which won't be bundled
|
||||||
|
val missingHelper = (knownExtensionFiles - enabledExtensionFiles).firstOrNull { rel ->
|
||||||
|
patchText.contains("L${rel.removeSuffix(".smali")};")
|
||||||
|
}
|
||||||
|
if (missingHelper != null) {
|
||||||
|
container.log("Skipping $patchFile: references gated-off helper class $missingHelper")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val lines = patchText.split('\n')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (directive in lines) {
|
for (directive in lines) {
|
||||||
@@ -92,18 +147,25 @@ class SmaliPatchStep(
|
|||||||
container.log("Recorded rl-locals bump: $smaliPath method≈\"$methodSubstring\" >= $newValue")
|
container.log("Recorded rl-locals bump: $smaliPath method≈\"$methodSubstring\" >= $newValue")
|
||||||
}
|
}
|
||||||
|
|
||||||
val targetLine = lines.firstOrNull { it.startsWith("--- a/") }
|
// Split into per-target sections — a single .patch may contain multiple
|
||||||
?: throw Error("Patch $patchFile is missing a '--- a/...' header")
|
// `--- a/...` blocks targeting different classes.
|
||||||
val fullClassName = targetLine
|
val sections = splitMultiTargetPatch(lines)
|
||||||
.removePrefix("--- a/")
|
if (sections.isEmpty()) {
|
||||||
.removeSuffix(".smali")
|
throw Error("Patch $patchFile is missing a '--- a/...' header")
|
||||||
.trim()
|
}
|
||||||
val patch = LoadedPatch(
|
for (section in sections) {
|
||||||
fullClassName = fullClassName,
|
val targetLine = section.first { it.startsWith("--- a/") }
|
||||||
patch = UnifiedDiffUtils.parseUnifiedDiff(lines),
|
val fullClassName = targetLine
|
||||||
)
|
.removePrefix("--- a/")
|
||||||
patches.add(patch)
|
.removeSuffix(".smali")
|
||||||
container.log("Loaded patch file $patchFile for class ${patch.fullClassName}")
|
.trim()
|
||||||
|
val patch = LoadedPatch(
|
||||||
|
fullClassName = fullClassName,
|
||||||
|
patch = UnifiedDiffUtils.parseUnifiedDiff(section),
|
||||||
|
)
|
||||||
|
patches.add(patch)
|
||||||
|
container.log("Loaded patch file $patchFile for class ${patch.fullClassName}")
|
||||||
|
}
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
throw Error("Failed to parse patch file $patchFile", t)
|
throw Error("Failed to parse patch file $patchFile", t)
|
||||||
}
|
}
|
||||||
@@ -313,6 +375,29 @@ class SmaliPatchStep(
|
|||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
val LOCALS_DIRECTIVE = Regex("""^#\s*rl-locals:\s+(\S+)\s+(\S+)\s+(\d+)\s*$""")
|
val LOCALS_DIRECTIVE = Regex("""^#\s*rl-locals:\s+(\S+)\s+(\S+)\s+(\d+)\s*$""")
|
||||||
|
val UNRESOLVED_TOKEN = Regex("""__RL_[A-Z0-9_]+__""")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits a unified diff into per-target sections. A single `.patch` file may bundle
|
||||||
|
* multiple file diffs (each starting with `--- a/...`); each section becomes its own
|
||||||
|
* patch with its own target class. The header lines before the first `--- a/` (and
|
||||||
|
* any `# rl-locals:` directives) are preserved by being copied into every section so
|
||||||
|
* that `UnifiedDiffUtils.parseUnifiedDiff` can still parse the section in isolation.
|
||||||
|
*/
|
||||||
|
private fun splitMultiTargetPatch(lines: List<String>): List<List<String>> {
|
||||||
|
val headerEnd = lines.indexOfFirst { it.startsWith("--- a/") }
|
||||||
|
if (headerEnd < 0) return emptyList()
|
||||||
|
val header = lines.subList(0, headerEnd)
|
||||||
|
|
||||||
|
val sectionStarts = lines.withIndex()
|
||||||
|
.filter { (_, line) -> line.startsWith("--- a/") }
|
||||||
|
.map { it.index }
|
||||||
|
|
||||||
|
return sectionStarts.mapIndexed { i, start ->
|
||||||
|
val end = sectionStarts.getOrNull(i + 1) ?: lines.size
|
||||||
|
header + lines.subList(start, end)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
-96
@@ -1,96 +0,0 @@
|
|||||||
package com.meowarex.rlmobile.ui.components.dialogs
|
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.material3.*
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
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.style.TextAlign
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.window.DialogProperties
|
|
||||||
import com.meowarex.rlmobile.R
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun NetworkWarningDialog(
|
|
||||||
onConfirm: (neverShow: Boolean) -> Unit,
|
|
||||||
onDismiss: (neverShow: Boolean) -> Unit,
|
|
||||||
) {
|
|
||||||
val interactionSource = remember(::MutableInteractionSource)
|
|
||||||
var neverShow by rememberSaveable { mutableStateOf(false) }
|
|
||||||
val rememberedNeverShow by rememberUpdatedState(neverShow)
|
|
||||||
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = { onDismiss(rememberedNeverShow) },
|
|
||||||
properties = DialogProperties(
|
|
||||||
dismissOnClickOutside = false,
|
|
||||||
),
|
|
||||||
confirmButton = {
|
|
||||||
FilledTonalButton(
|
|
||||||
onClick = { onConfirm(rememberedNeverShow) },
|
|
||||||
colors = ButtonDefaults.filledTonalButtonColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.error,
|
|
||||||
contentColor = MaterialTheme.colorScheme.onError,
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.action_continue))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(
|
|
||||||
onClick = { onDismiss(rememberedNeverShow) },
|
|
||||||
colors = ButtonDefaults.textButtonColors(
|
|
||||||
contentColor = MaterialTheme.colorScheme.onErrorContainer
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.navigation_back))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
title = { Text(stringResource(R.string.network_warning_title)) },
|
|
||||||
text = {
|
|
||||||
Column(
|
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.network_warning_body),
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
)
|
|
||||||
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable(
|
|
||||||
interactionSource = interactionSource,
|
|
||||||
indication = null,
|
|
||||||
onClick = { neverShow = !rememberedNeverShow },
|
|
||||||
)
|
|
||||||
.padding(end = 16.dp)
|
|
||||||
) {
|
|
||||||
Checkbox(
|
|
||||||
checked = neverShow,
|
|
||||||
onCheckedChange = { neverShow = it },
|
|
||||||
interactionSource = interactionSource,
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(stringResource(R.string.network_warning_disable))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
icon = {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(R.drawable.ic_warning),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(32.dp),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
containerColor = MaterialTheme.colorScheme.errorContainer,
|
|
||||||
iconContentColor = MaterialTheme.colorScheme.onErrorContainer,
|
|
||||||
titleContentColor = MaterialTheme.colorScheme.onErrorContainer,
|
|
||||||
textContentColor = MaterialTheme.colorScheme.onErrorContainer,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
-19
@@ -1,19 +0,0 @@
|
|||||||
package com.meowarex.rlmobile.ui.previews.dialogs
|
|
||||||
|
|
||||||
import android.content.res.Configuration
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import com.meowarex.rlmobile.ui.screens.logs.components.dialogs.DeleteLogsDialog
|
|
||||||
import com.meowarex.rlmobile.ui.theme.ManagerTheme
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
|
||||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
|
|
||||||
private fun DeleteLogsDialogPreview() {
|
|
||||||
ManagerTheme {
|
|
||||||
DeleteLogsDialog(
|
|
||||||
onConfirm = {},
|
|
||||||
onDismiss = {},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-19
@@ -1,19 +0,0 @@
|
|||||||
package com.meowarex.rlmobile.ui.previews.dialogs
|
|
||||||
|
|
||||||
import android.content.res.Configuration
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import com.meowarex.rlmobile.ui.components.dialogs.NetworkWarningDialog
|
|
||||||
import com.meowarex.rlmobile.ui.theme.ManagerTheme
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
|
||||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
|
|
||||||
private fun NetworkWarningDialogPreview() {
|
|
||||||
ManagerTheme {
|
|
||||||
NetworkWarningDialog(
|
|
||||||
onConfirm = {},
|
|
||||||
onDismiss = {},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+12
-6
@@ -30,6 +30,12 @@ private fun ComponentOptionsScreenPreview(
|
|||||||
selected = parameters.selected,
|
selected = parameters.selected,
|
||||||
onSelectComponent = {},
|
onSelectComponent = {},
|
||||||
onDeleteComponent = {},
|
onDeleteComponent = {},
|
||||||
|
onImportFromUri = {},
|
||||||
|
releasesExpanded = false,
|
||||||
|
releasesState = com.meowarex.rlmobile.ui.screens.componentopts.ComponentOptionsModel.ReleasesState.Idle,
|
||||||
|
onToggleReleases = {},
|
||||||
|
onImportRelease = {},
|
||||||
|
importingReleaseTag = null,
|
||||||
onBackPressed = {},
|
onBackPressed = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -44,27 +50,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 +78,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,
|
||||||
),
|
),
|
||||||
|
|||||||
+18
-8
@@ -2,6 +2,8 @@ package com.meowarex.rlmobile.ui.previews.screens
|
|||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.tooling.preview.*
|
import androidx.compose.ui.tooling.preview.*
|
||||||
import com.meowarex.rlmobile.network.utils.SemVer
|
import com.meowarex.rlmobile.network.utils.SemVer
|
||||||
import com.meowarex.rlmobile.ui.screens.componentopts.PatchComponent
|
import com.meowarex.rlmobile.ui.screens.componentopts.PatchComponent
|
||||||
@@ -18,6 +20,9 @@ private fun PatchOptionsScreenPreview(
|
|||||||
@PreviewParameter(PatchOptionsParametersProvider::class)
|
@PreviewParameter(PatchOptionsParametersProvider::class)
|
||||||
parameters: PatchOptionsParameters,
|
parameters: PatchOptionsParameters,
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val specs = remember { builtinPatchSpecs { context.getString(it) } }
|
||||||
|
|
||||||
ManagerTheme {
|
ManagerTheme {
|
||||||
PatchOptionsScreenContent(
|
PatchOptionsScreenContent(
|
||||||
isUpdate = parameters.isUpdate,
|
isUpdate = parameters.isUpdate,
|
||||||
@@ -30,13 +35,18 @@ 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,
|
specs = specs,
|
||||||
|
enabledPatchCount = specs.size,
|
||||||
isPatchEnabled = { true },
|
isPatchEnabled = { true },
|
||||||
onTogglePatch = { _, _ -> },
|
onTogglePatch = { _, _ -> },
|
||||||
|
patchLockState = { PatchLock.Free },
|
||||||
|
variantIndex = { 0 },
|
||||||
|
onSelectVariant = { _, _ -> },
|
||||||
|
optionState = PatchOptionState.Preview,
|
||||||
isConfigValid = parameters.isConfigValid,
|
isConfigValid = parameters.isConfigValid,
|
||||||
onInstall = {},
|
onInstall = {},
|
||||||
)
|
)
|
||||||
@@ -51,7 +61,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 +76,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 +88,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 +100,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(),
|
||||||
),
|
),
|
||||||
|
|||||||
-78
@@ -1,78 +0,0 @@
|
|||||||
package com.meowarex.rlmobile.ui.previews.screens.logs
|
|
||||||
|
|
||||||
import android.content.res.Configuration
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
|
||||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import com.meowarex.rlmobile.ui.screens.logs.LogEntry
|
|
||||||
import com.meowarex.rlmobile.ui.screens.logs.LogsScreenContent
|
|
||||||
import com.meowarex.rlmobile.ui.theme.ManagerTheme
|
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
|
||||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
|
|
||||||
private fun LogsListScreenNonePreview() {
|
|
||||||
ManagerTheme {
|
|
||||||
LogsScreenContent(
|
|
||||||
logs = logs,
|
|
||||||
onOpenLog = {},
|
|
||||||
onDeleteLogs = {},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val logs: SnapshotStateList<LogEntry> = mutableStateListOf(
|
|
||||||
LogEntry(
|
|
||||||
id = UUID.randomUUID().toString(),
|
|
||||||
isError = false,
|
|
||||||
installDate = "5 min. ago, 10:18 AM",
|
|
||||||
durationSecs = 18.555f,
|
|
||||||
stacktracePreview = null,
|
|
||||||
),
|
|
||||||
LogEntry(
|
|
||||||
id = UUID.randomUUID().toString(),
|
|
||||||
isError = true,
|
|
||||||
installDate = "7 min. ago, 10:17 AM",
|
|
||||||
durationSecs = 73.095f,
|
|
||||||
stacktracePreview = persistentListOf(
|
|
||||||
"kotlinx.coroutines.JobCancellationException: Job was cancelled; job=SupervisorJobImpl{Cancelling}@833e76f]",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
LogEntry(
|
|
||||||
id = UUID.randomUUID().toString(),
|
|
||||||
isError = false,
|
|
||||||
installDate = "Yesterday, 11:37 PM",
|
|
||||||
durationSecs = 58.439f,
|
|
||||||
stacktracePreview = null,
|
|
||||||
),
|
|
||||||
LogEntry(
|
|
||||||
id = UUID.randomUUID().toString(),
|
|
||||||
isError = true,
|
|
||||||
installDate = "Yesterday, 11:17 PM",
|
|
||||||
durationSecs = 24.405f,
|
|
||||||
stacktracePreview = persistentListOf(
|
|
||||||
"java.lang.Error: Installation was aborted or cancelled",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
LogEntry(
|
|
||||||
id = UUID.randomUUID().toString(),
|
|
||||||
isError = true,
|
|
||||||
installDate = "Yesterday, 11:17 PM",
|
|
||||||
durationSecs = 0.057f,
|
|
||||||
stacktracePreview = persistentListOf(
|
|
||||||
"java.lang.IllegalStateException: balls",
|
|
||||||
"\tat com.meowarex.rlmobile.patcher.steps.prepare.FetchInfoStep.execute(FetchInfoStep.kt:31)",
|
|
||||||
"\tat com.meowarex.rlmobile.patcher.steps.prepare.FetchInfoStep\$execute\\$1.invokeSuspend(Unknown Source:15)",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
LogEntry(
|
|
||||||
id = UUID.randomUUID().toString(),
|
|
||||||
isError = false,
|
|
||||||
installDate = "Yesterday, 1:11 PM",
|
|
||||||
durationSecs = 210.539f,
|
|
||||||
stacktracePreview = null,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
-20
@@ -1,20 +0,0 @@
|
|||||||
package com.meowarex.rlmobile.ui.previews.screens.logs
|
|
||||||
|
|
||||||
import android.content.res.Configuration
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import com.meowarex.rlmobile.ui.screens.logs.LogsScreenContent
|
|
||||||
import com.meowarex.rlmobile.ui.theme.ManagerTheme
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
|
||||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
|
|
||||||
private fun LogsListScreenNonePreview() {
|
|
||||||
ManagerTheme {
|
|
||||||
LogsScreenContent(
|
|
||||||
logs = remember { mutableStateListOf() },
|
|
||||||
onOpenLog = {},
|
|
||||||
onDeleteLogs = {},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+169
-1
@@ -1,26 +1,44 @@
|
|||||||
package com.meowarex.rlmobile.ui.screens.componentopts
|
package com.meowarex.rlmobile.ui.screens.componentopts
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.net.Uri
|
||||||
|
import android.provider.OpenableColumns
|
||||||
|
import android.util.Log
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
|
import com.meowarex.rlmobile.BuildConfig
|
||||||
import com.meowarex.rlmobile.R
|
import com.meowarex.rlmobile.R
|
||||||
import com.meowarex.rlmobile.manager.PathManager
|
import com.meowarex.rlmobile.manager.PathManager
|
||||||
|
import com.meowarex.rlmobile.manager.download.KtorDownloadManager
|
||||||
|
import com.meowarex.rlmobile.network.models.GithubRelease
|
||||||
|
import com.meowarex.rlmobile.network.services.RadiantLyricsGithubService
|
||||||
import com.meowarex.rlmobile.network.utils.SemVer
|
import com.meowarex.rlmobile.network.utils.SemVer
|
||||||
|
import com.meowarex.rlmobile.network.utils.fold
|
||||||
import com.meowarex.rlmobile.ui.util.ScreenModelWithResult
|
import com.meowarex.rlmobile.ui.util.ScreenModelWithResult
|
||||||
import com.meowarex.rlmobile.ui.util.ScreenResultKey
|
import com.meowarex.rlmobile.ui.util.ScreenResultKey
|
||||||
import com.meowarex.rlmobile.util.*
|
import com.meowarex.rlmobile.util.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.File
|
||||||
import kotlin.time.Instant
|
import kotlin.time.Instant
|
||||||
|
|
||||||
class ComponentOptionsModel(
|
class ComponentOptionsModel(
|
||||||
screenResultKey: ScreenResultKey,
|
screenResultKey: ScreenResultKey,
|
||||||
private val paths: PathManager,
|
private val paths: PathManager,
|
||||||
private val context: Application,
|
private val context: Application,
|
||||||
|
private val github: RadiantLyricsGithubService,
|
||||||
|
private val downloader: KtorDownloadManager,
|
||||||
) : ScreenModelWithResult<PatchComponent?>(screenResultKey) {
|
) : ScreenModelWithResult<PatchComponent?>(screenResultKey) {
|
||||||
val components = mutableStateListOf<PatchComponent>()
|
val components = mutableStateListOf<PatchComponent>()
|
||||||
var selected by mutableStateOf<PatchComponent?>(null)
|
var selected by mutableStateOf<PatchComponent?>(null)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
var releasesExpanded by mutableStateOf(false)
|
||||||
|
private set
|
||||||
|
var releasesState by mutableStateOf<ReleasesState>(ReleasesState.Idle)
|
||||||
|
private set
|
||||||
|
var importingReleaseTag by mutableStateOf<String?>(null)
|
||||||
|
private set
|
||||||
|
|
||||||
fun selectComponent(component: PatchComponent?) {
|
fun selectComponent(component: PatchComponent?) {
|
||||||
selected = component
|
selected = component
|
||||||
}
|
}
|
||||||
@@ -39,7 +57,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()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +82,157 @@ class ComponentOptionsModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun importFromUri(uri: Uri, type: PatchComponent.Type) = screenModelScope.launchIO {
|
||||||
|
try {
|
||||||
|
val targetDir = when (type) {
|
||||||
|
PatchComponent.Type.TidalApk -> paths.customTidalApksDir
|
||||||
|
PatchComponent.Type.Patches -> paths.customPatchesDir
|
||||||
|
}
|
||||||
|
val ext = when (type) {
|
||||||
|
PatchComponent.Type.TidalApk -> "apk"
|
||||||
|
PatchComponent.Type.Patches -> "zip"
|
||||||
|
}
|
||||||
|
targetDir.mkdirs()
|
||||||
|
|
||||||
|
val tempFile = targetDir.resolve("import-${System.currentTimeMillis()}.tmp")
|
||||||
|
context.contentResolver.openInputStream(uri)?.use { input ->
|
||||||
|
tempFile.outputStream().use { output -> input.copyTo(output) }
|
||||||
|
} ?: throw IllegalStateException("Could not open input stream for $uri")
|
||||||
|
|
||||||
|
val sourceDisplayName = queryDisplayName(uri)
|
||||||
|
val version = when (type) {
|
||||||
|
PatchComponent.Type.TidalApk -> readApkVersion(tempFile)
|
||||||
|
?: extractVersionFromName(sourceDisplayName)
|
||||||
|
?: FALLBACK_VERSION
|
||||||
|
PatchComponent.Type.Patches -> extractVersionFromName(sourceDisplayName)
|
||||||
|
?: FALLBACK_VERSION
|
||||||
|
}
|
||||||
|
|
||||||
|
val finalName = "${System.currentTimeMillis()}_$version.$ext"
|
||||||
|
val finalFile = targetDir.resolve(finalName)
|
||||||
|
if (!tempFile.renameTo(finalFile)) {
|
||||||
|
tempFile.copyTo(finalFile, overwrite = true)
|
||||||
|
tempFile.delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshComponents(type)
|
||||||
|
mainThread { context.showToast(R.string.intent_import_component_success, finalName) }
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
Log.e(BuildConfig.TAG, "Failed to import custom component from $uri", t)
|
||||||
|
mainThread { context.showToast(R.string.intent_import_component_failure) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun queryDisplayName(uri: Uri): String? = try {
|
||||||
|
context.contentResolver.query(uri, arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null)
|
||||||
|
?.use { cursor ->
|
||||||
|
if (cursor.moveToFirst()) cursor.getString(0) else null
|
||||||
|
}
|
||||||
|
} catch (_: Throwable) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readApkVersion(apkFile: File): String? = try {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
context.packageManager.getPackageArchiveInfo(apkFile.absolutePath, 0)
|
||||||
|
?.versionName
|
||||||
|
?.let { SemVer.parseOrNull(it) }
|
||||||
|
?.toString()
|
||||||
|
} catch (_: Throwable) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractVersionFromName(name: String?): String? {
|
||||||
|
if (name == null) return null
|
||||||
|
return """(\d+\.\d+\.\d+)""".toRegex().find(name)?.value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleReleasesExpanded(type: PatchComponent.Type) {
|
||||||
|
releasesExpanded = !releasesExpanded
|
||||||
|
if (releasesExpanded && releasesState is ReleasesState.Idle) {
|
||||||
|
loadReleases(type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadReleases(type: PatchComponent.Type) = screenModelScope.launchIO {
|
||||||
|
releasesState = ReleasesState.Loading
|
||||||
|
github.getManagerReleases().fold(
|
||||||
|
success = { all ->
|
||||||
|
val assetName = assetNameFor(type)
|
||||||
|
val filtered = all.filter { release ->
|
||||||
|
release.assets.any { it.name == assetName }
|
||||||
|
}
|
||||||
|
releasesState = ReleasesState.Loaded(filtered)
|
||||||
|
},
|
||||||
|
fail = {
|
||||||
|
Log.w(BuildConfig.TAG, "Failed to load GitHub releases", it)
|
||||||
|
releasesState = ReleasesState.Failed
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun importFromRelease(release: GithubRelease, type: PatchComponent.Type) = screenModelScope.launchIO {
|
||||||
|
val assetName = assetNameFor(type)
|
||||||
|
val asset = release.assets.find { it.name == assetName } ?: run {
|
||||||
|
mainThread { context.showToast(R.string.intent_import_component_failure) }
|
||||||
|
return@launchIO
|
||||||
|
}
|
||||||
|
|
||||||
|
val targetDir = when (type) {
|
||||||
|
PatchComponent.Type.TidalApk -> paths.customTidalApksDir
|
||||||
|
PatchComponent.Type.Patches -> paths.customPatchesDir
|
||||||
|
}
|
||||||
|
targetDir.mkdirs()
|
||||||
|
|
||||||
|
importingReleaseTag = release.tagName
|
||||||
|
try {
|
||||||
|
val tempFile = targetDir.resolve("release-${System.currentTimeMillis()}.tmp")
|
||||||
|
val result = downloader.download(asset.browserDownloadUrl, tempFile)
|
||||||
|
if (result !is com.meowarex.rlmobile.manager.download.IDownloadManager.Result.Success) {
|
||||||
|
tempFile.delete()
|
||||||
|
mainThread { context.showToast(R.string.intent_import_component_failure) }
|
||||||
|
return@launchIO
|
||||||
|
}
|
||||||
|
|
||||||
|
val ext = when (type) {
|
||||||
|
PatchComponent.Type.TidalApk -> "apk"
|
||||||
|
PatchComponent.Type.Patches -> "zip"
|
||||||
|
}
|
||||||
|
val version = SemVer.parseOrNull(release.tagName.removePrefix("v"))?.toString()
|
||||||
|
?: FALLBACK_VERSION
|
||||||
|
val finalName = "${System.currentTimeMillis()}_$version.$ext"
|
||||||
|
val finalFile = targetDir.resolve(finalName)
|
||||||
|
if (!tempFile.renameTo(finalFile)) {
|
||||||
|
tempFile.copyTo(finalFile, overwrite = true)
|
||||||
|
tempFile.delete()
|
||||||
|
}
|
||||||
|
refreshComponents(type)
|
||||||
|
mainThread { context.showToast(R.string.intent_import_component_success, finalName) }
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
Log.e(BuildConfig.TAG, "Failed to import release ${release.tagName}", t)
|
||||||
|
mainThread { context.showToast(R.string.intent_import_component_failure) }
|
||||||
|
} finally {
|
||||||
|
importingReleaseTag = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assetNameFor(type: PatchComponent.Type) = when (type) {
|
||||||
|
PatchComponent.Type.TidalApk -> "tidal-stock.apk"
|
||||||
|
PatchComponent.Type.Patches -> "patches.zip"
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDispose() {
|
override fun onDispose() {
|
||||||
screenModelScope.launch { setResult(selected) }
|
screenModelScope.launch { setResult(selected) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed interface ReleasesState {
|
||||||
|
data object Idle : ReleasesState
|
||||||
|
data object Loading : ReleasesState
|
||||||
|
data class Loaded(val releases: List<GithubRelease>) : ReleasesState
|
||||||
|
data object Failed : ReleasesState
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val FALLBACK_VERSION = "0.0.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+232
@@ -1,13 +1,20 @@
|
|||||||
package com.meowarex.rlmobile.ui.screens.componentopts
|
package com.meowarex.rlmobile.ui.screens.componentopts
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import cafe.adriel.voyager.koin.koinScreenModel
|
import cafe.adriel.voyager.koin.koinScreenModel
|
||||||
@@ -61,6 +68,12 @@ class ComponentOptionsScreen(
|
|||||||
selected = model.selected,
|
selected = model.selected,
|
||||||
onSelectComponent = model::selectComponent,
|
onSelectComponent = model::selectComponent,
|
||||||
onDeleteComponent = model::deleteComponent,
|
onDeleteComponent = model::deleteComponent,
|
||||||
|
onImportFromUri = { uri -> model.importFromUri(uri, componentType) },
|
||||||
|
releasesExpanded = model.releasesExpanded,
|
||||||
|
releasesState = model.releasesState,
|
||||||
|
onToggleReleases = { model.toggleReleasesExpanded(componentType) },
|
||||||
|
onImportRelease = { release -> model.importFromRelease(release, componentType) },
|
||||||
|
importingReleaseTag = model.importingReleaseTag,
|
||||||
onBackPressed = { navigator.back(null) },
|
onBackPressed = { navigator.back(null) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -73,8 +86,22 @@ fun ComponentOptionsScreenContent(
|
|||||||
selected: PatchComponent?,
|
selected: PatchComponent?,
|
||||||
onSelectComponent: (PatchComponent?) -> Unit,
|
onSelectComponent: (PatchComponent?) -> Unit,
|
||||||
onDeleteComponent: (PatchComponent) -> Unit,
|
onDeleteComponent: (PatchComponent) -> Unit,
|
||||||
|
onImportFromUri: (Uri) -> Unit,
|
||||||
|
releasesExpanded: Boolean,
|
||||||
|
releasesState: ComponentOptionsModel.ReleasesState,
|
||||||
|
onToggleReleases: () -> Unit,
|
||||||
|
onImportRelease: (com.meowarex.rlmobile.network.models.GithubRelease) -> Unit,
|
||||||
|
importingReleaseTag: String?,
|
||||||
onBackPressed: () -> Unit,
|
onBackPressed: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val mime = when (componentType) {
|
||||||
|
PatchComponent.Type.TidalApk -> "application/vnd.android.package-archive"
|
||||||
|
PatchComponent.Type.Patches -> "application/zip"
|
||||||
|
}
|
||||||
|
val filePicker = rememberLauncherForActivityResult(
|
||||||
|
ActivityResultContracts.OpenDocument(),
|
||||||
|
) { uri -> uri?.let(onImportFromUri) }
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { ComponentOptionsAppBar(componentType = componentType) },
|
topBar = { ComponentOptionsAppBar(componentType = componentType) },
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
@@ -98,6 +125,16 @@ fun ComponentOptionsScreenContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
item(key = "RELEASES_ACCORDION") {
|
||||||
|
ReleasesAccordion(
|
||||||
|
expanded = releasesExpanded,
|
||||||
|
state = releasesState,
|
||||||
|
importingTag = importingReleaseTag,
|
||||||
|
onToggle = onToggleReleases,
|
||||||
|
onImport = onImportRelease,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
items(
|
items(
|
||||||
items = components,
|
items = components,
|
||||||
contentType = { "COMPONENT" },
|
contentType = { "COMPONENT" },
|
||||||
@@ -112,6 +149,10 @@ fun ComponentOptionsScreenContent(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
item(key = "BROWSE") {
|
||||||
|
BrowseImportCard(onClick = { filePicker.launch(arrayOf(mime)) })
|
||||||
|
}
|
||||||
|
|
||||||
item("EXIT_BTN") {
|
item("EXIT_BTN") {
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.End,
|
horizontalArrangement = Arrangement.End,
|
||||||
@@ -129,3 +170,194 @@ fun ComponentOptionsScreenContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ReleasesAccordion(
|
||||||
|
expanded: Boolean,
|
||||||
|
state: ComponentOptionsModel.ReleasesState,
|
||||||
|
importingTag: String?,
|
||||||
|
onToggle: () -> Unit,
|
||||||
|
onImport: (com.meowarex.rlmobile.network.models.GithubRelease) -> Unit,
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(14.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable(onClick = onToggle)
|
||||||
|
.padding(horizontal = 16.dp, vertical = 14.dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_account_github_white_24dp),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
)
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.componentopts_releases_title),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.componentopts_releases_desc),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = LocalContentColor.current.copy(alpha = 0.65f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(
|
||||||
|
if (expanded) R.drawable.ic_arrow_up_small else R.drawable.ic_arrow_down_small
|
||||||
|
),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = LocalContentColor.current.copy(alpha = 0.7f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expanded) {
|
||||||
|
HorizontalDivider(
|
||||||
|
color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.35f),
|
||||||
|
)
|
||||||
|
ReleasesContent(
|
||||||
|
state = state,
|
||||||
|
importingTag = importingTag,
|
||||||
|
onImport = onImport,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ReleasesContent(
|
||||||
|
state: ComponentOptionsModel.ReleasesState,
|
||||||
|
importingTag: String?,
|
||||||
|
onImport: (com.meowarex.rlmobile.network.models.GithubRelease) -> Unit,
|
||||||
|
) {
|
||||||
|
when (state) {
|
||||||
|
is ComponentOptionsModel.ReleasesState.Idle,
|
||||||
|
ComponentOptionsModel.ReleasesState.Loading -> Box(
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(20.dp),
|
||||||
|
) { CircularProgressIndicator(modifier = Modifier.size(24.dp)) }
|
||||||
|
|
||||||
|
ComponentOptionsModel.ReleasesState.Failed -> Text(
|
||||||
|
text = stringResource(R.string.network_load_fail),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.error,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(20.dp),
|
||||||
|
)
|
||||||
|
|
||||||
|
is ComponentOptionsModel.ReleasesState.Loaded -> {
|
||||||
|
if (state.releases.isEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.componentopts_releases_empty),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = LocalContentColor.current.copy(alpha = 0.65f),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(20.dp),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Column {
|
||||||
|
state.releases.forEachIndexed { index, release ->
|
||||||
|
ReleaseRow(
|
||||||
|
release = release,
|
||||||
|
importing = importingTag == release.tagName,
|
||||||
|
anyImporting = importingTag != null,
|
||||||
|
onImport = { onImport(release) },
|
||||||
|
)
|
||||||
|
if (index != state.releases.lastIndex) {
|
||||||
|
HorizontalDivider(
|
||||||
|
color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.2f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ReleaseRow(
|
||||||
|
release: com.meowarex.rlmobile.network.models.GithubRelease,
|
||||||
|
importing: Boolean,
|
||||||
|
anyImporting: Boolean,
|
||||||
|
onImport: () -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = release.tagName,
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = release.name ?: release.tagName,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = LocalContentColor.current.copy(alpha = 0.55f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
FilledTonalButton(
|
||||||
|
onClick = onImport,
|
||||||
|
enabled = !anyImporting,
|
||||||
|
) {
|
||||||
|
if (importing) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
strokeWidth = 2.dp,
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(stringResource(R.string.action_install))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun BrowseImportCard(onClick: () -> Unit) {
|
||||||
|
Surface(
|
||||||
|
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable(onClick = onClick),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(14.dp),
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 14.dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_add),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
)
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.componentopts_browse_title),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.componentopts_browse_desc),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = LocalContentColor.current.copy(alpha = 0.65f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+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,110 @@ 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 installedPatches = installMetadata?.patchesVersion?.toString()
|
||||||
|
val previousPatches = initialPrefPatchesVersion.ifEmpty {
|
||||||
|
installedPatches.orEmpty()
|
||||||
|
}
|
||||||
|
val patchesFrom = previousPatches.takeIf { it.isNotEmpty() }
|
||||||
|
val patchesTo = currentPatches ?: previousPatches.ifEmpty { "?" }
|
||||||
|
val patchesUpdateAvailable = installedPatches != null
|
||||||
|
&& currentPatches != null
|
||||||
|
&& installedPatches != currentPatches
|
||||||
|
add(
|
||||||
|
VersionDelta(
|
||||||
|
label = application.getString(R.string.manager_update_row_patches),
|
||||||
|
iconRes = R.drawable.ic_extension,
|
||||||
|
from = patchesFrom,
|
||||||
|
to = patchesTo,
|
||||||
|
tag = if (patchesUpdateAvailable)
|
||||||
|
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()
|
||||||
|
?: "?"
|
||||||
|
val tidalUpdateAvailable = installedTidalVersionCode > 0
|
||||||
|
&& currentTidal != null
|
||||||
|
&& installedTidalVersionCode != currentTidal
|
||||||
|
add(
|
||||||
|
VersionDelta(
|
||||||
|
label = application.getString(R.string.manager_update_row_tidal),
|
||||||
|
iconRes = R.drawable.ic_music_note,
|
||||||
|
from = tidalFrom,
|
||||||
|
to = tidalTo,
|
||||||
|
tag = if (tidalUpdateAvailable)
|
||||||
|
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 +181,7 @@ class HomeModel(
|
|||||||
install = install,
|
install = install,
|
||||||
latestTidalVersionCode = latest,
|
latestTidalVersionCode = latest,
|
||||||
)
|
)
|
||||||
|
maybeCheckManagerUpdate(pkg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,7 +196,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 +219,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 +246,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 +279,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 +294,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.asImageBitmap
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
import androidx.compose.ui.graphics.painter.BitmapPainter
|
import androidx.compose.ui.graphics.painter.BitmapPainter
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
@@ -29,14 +30,17 @@ import cafe.adriel.voyager.navigator.LocalNavigator
|
|||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import com.meowarex.rlmobile.R
|
import com.meowarex.rlmobile.R
|
||||||
import com.meowarex.rlmobile.ui.components.SegmentedButton
|
import com.meowarex.rlmobile.ui.components.SegmentedButton
|
||||||
|
import com.meowarex.rlmobile.ui.components.Tag
|
||||||
import com.meowarex.rlmobile.ui.screens.about.AboutScreen
|
import com.meowarex.rlmobile.ui.screens.about.AboutScreen
|
||||||
import com.meowarex.rlmobile.ui.screens.home.components.CommitList
|
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.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.ui.widgets.updater.UpdaterViewModel
|
||||||
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
|
||||||
|
import org.koin.androidx.compose.koinViewModel
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
class HomeScreen : Screen, Parcelable {
|
class HomeScreen : Screen, Parcelable {
|
||||||
@@ -48,6 +52,9 @@ class HomeScreen : Screen, Parcelable {
|
|||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val model = koinScreenModel<HomeModel>()
|
val model = koinScreenModel<HomeModel>()
|
||||||
|
val activity = LocalContext.current as ComponentActivity
|
||||||
|
val updater = koinViewModel<UpdaterViewModel>(viewModelStoreOwner = activity)
|
||||||
|
val managerUpdateAvailable = updater.targetVersion != null
|
||||||
|
|
||||||
LifecycleResumeEffect(Unit) {
|
LifecycleResumeEffect(Unit) {
|
||||||
model.refresh(delay = true)
|
model.refresh(delay = true)
|
||||||
@@ -65,18 +72,20 @@ class HomeScreen : Screen, Parcelable {
|
|||||||
contentDescription = stringResource(R.string.navigation_refresh),
|
contentDescription = stringResource(R.string.navigation_refresh),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (managerUpdateAvailable) {
|
||||||
|
IconButton(onClick = updater::reopenDialog) {
|
||||||
|
Icon(
|
||||||
|
painterResource(R.drawable.ic_update),
|
||||||
|
contentDescription = stringResource(R.string.action_update),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
IconButton(onClick = { navigator.push(AboutScreen()) }) {
|
IconButton(onClick = { navigator.push(AboutScreen()) }) {
|
||||||
Icon(
|
Icon(
|
||||||
painterResource(R.drawable.ic_info),
|
painterResource(R.drawable.ic_info),
|
||||||
contentDescription = stringResource(R.string.navigation_about),
|
contentDescription = stringResource(R.string.navigation_about),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
IconButton(onClick = { navigator.push(LogsListScreen()) }) {
|
|
||||||
Icon(
|
|
||||||
painterResource(R.drawable.ic_receipt),
|
|
||||||
contentDescription = stringResource(R.string.navigation_logs),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
IconButton(onClick = { navigator.push(SettingsScreen()) }) {
|
IconButton(onClick = { navigator.push(SettingsScreen()) }) {
|
||||||
Icon(
|
Icon(
|
||||||
painterResource(R.drawable.ic_settings),
|
painterResource(R.drawable.ic_settings),
|
||||||
@@ -105,10 +114,11 @@ class HomeScreen : Screen, Parcelable {
|
|||||||
is HomeState.Loaded -> HomeContent(
|
is HomeState.Loaded -> HomeContent(
|
||||||
state = state,
|
state = state,
|
||||||
commits = model.commits,
|
commits = model.commits,
|
||||||
|
managerUpdateAvailable = managerUpdateAvailable,
|
||||||
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 +126,13 @@ class HomeScreen : Screen, Parcelable {
|
|||||||
onInfo = model::openCurrentAppInfo,
|
onInfo = model::openCurrentAppInfo,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model.managerUpdateDeltas?.let { deltas ->
|
||||||
|
ManagerUpdateDialog(
|
||||||
|
deltas = deltas,
|
||||||
|
onDismiss = model::dismissManagerUpdate,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,13 +142,15 @@ class HomeScreen : Screen, Parcelable {
|
|||||||
private fun ColumnScope.HomeContent(
|
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>>,
|
||||||
|
managerUpdateAvailable: Boolean,
|
||||||
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,16 +200,29 @@ 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) Tag(text = "New Patches!")
|
||||||
|
if (tidalBehind) Tag(text = "TIDAL Update!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val blockedByManagerUpdate = managerUpdateAvailable && (patchesBehind || tidalBehind)
|
||||||
Button(
|
Button(
|
||||||
onClick = if (install == null) onInstall else onReinstall,
|
onClick = if (install == null) onInstall else onRepatch,
|
||||||
enabled = state.latestTidalVersionCode != null,
|
enabled = state.latestTidalVersionCode != null && !blockedByManagerUpdate,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
val label = when {
|
val label = when {
|
||||||
|
blockedByManagerUpdate -> "Manager Update Required"
|
||||||
state.latestTidalVersionCode == null -> "Loading…"
|
state.latestTidalVersionCode == null -> "Loading…"
|
||||||
install == null -> "Install"
|
install == null -> "Install"
|
||||||
install.isUpToDate == false -> "Update"
|
patchesBehind && tidalBehind -> "Update Patches & TIDAL"
|
||||||
else -> "Reinstall"
|
patchesBehind -> "Update Patches"
|
||||||
|
tidalBehind -> "Update TIDAL"
|
||||||
|
else -> "Repatch"
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
text = label,
|
text = label,
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
package com.meowarex.rlmobile.ui.screens.logs
|
|
||||||
|
|
||||||
import android.os.Parcelable
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
|
||||||
import cafe.adriel.voyager.core.screen.ScreenKey
|
|
||||||
import cafe.adriel.voyager.koin.koinScreenModel
|
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
|
||||||
import com.meowarex.rlmobile.ui.screens.log.LogScreen
|
|
||||||
import com.meowarex.rlmobile.ui.screens.logs.components.*
|
|
||||||
import com.meowarex.rlmobile.ui.screens.logs.components.dialogs.DeleteLogsDialog
|
|
||||||
import com.meowarex.rlmobile.ui.util.paddings.PaddingValuesSides
|
|
||||||
import com.meowarex.rlmobile.ui.util.paddings.exclude
|
|
||||||
import kotlinx.parcelize.IgnoredOnParcel
|
|
||||||
import kotlinx.parcelize.Parcelize
|
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
class LogsListScreen : Screen, Parcelable {
|
|
||||||
@IgnoredOnParcel
|
|
||||||
override val key: ScreenKey
|
|
||||||
get() = "LogsScreen"
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun Content() {
|
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
|
||||||
val model = koinScreenModel<LogsListScreenModel>()
|
|
||||||
|
|
||||||
var showWipeConfirmDialog by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
if (showWipeConfirmDialog) {
|
|
||||||
DeleteLogsDialog(
|
|
||||||
onConfirm = {
|
|
||||||
showWipeConfirmDialog = false
|
|
||||||
model.deleteLogs()
|
|
||||||
},
|
|
||||||
onDismiss = {
|
|
||||||
showWipeConfirmDialog = false
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
LogsScreenContent(
|
|
||||||
logs = model.logEntries,
|
|
||||||
onOpenLog = { navigator.push(LogScreen(installId = it)) },
|
|
||||||
onDeleteLogs = { showWipeConfirmDialog = true },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun LogsScreenContent(
|
|
||||||
logs: SnapshotStateList<LogEntry>,
|
|
||||||
onOpenLog: (id: String) -> Unit,
|
|
||||||
onDeleteLogs: () -> Unit,
|
|
||||||
) {
|
|
||||||
Scaffold(
|
|
||||||
topBar = {
|
|
||||||
LogsListAppBar(
|
|
||||||
onDeleteLogs = onDeleteLogs,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
) { paddingValues ->
|
|
||||||
LazyColumn(
|
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
|
||||||
contentPadding = paddingValues.exclude(PaddingValuesSides.Horizontal + PaddingValuesSides.Top),
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(paddingValues.exclude(PaddingValuesSides.Bottom))
|
|
||||||
.padding(vertical = 12.dp, horizontal = 22.dp)
|
|
||||||
) {
|
|
||||||
if (logs.isEmpty()) {
|
|
||||||
item(key = "EMPTY") {
|
|
||||||
LogsNone(
|
|
||||||
modifier = Modifier.fillParentMaxSize(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
items(
|
|
||||||
items = logs,
|
|
||||||
contentType = { "LOG" },
|
|
||||||
key = { it.id },
|
|
||||||
) { data ->
|
|
||||||
LogEntryCard(
|
|
||||||
data = data,
|
|
||||||
onClick = remember(onOpenLog, data.id) { { onOpenLog(data.id) } },
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-71
@@ -1,71 +0,0 @@
|
|||||||
package com.meowarex.rlmobile.ui.screens.logs
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.text.format.DateUtils
|
|
||||||
import androidx.compose.runtime.Immutable
|
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
|
||||||
import cafe.adriel.voyager.core.model.ScreenModel
|
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
|
||||||
import com.meowarex.rlmobile.R
|
|
||||||
import com.meowarex.rlmobile.manager.InstallLogManager
|
|
||||||
import com.meowarex.rlmobile.util.*
|
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
|
||||||
|
|
||||||
class LogsListScreenModel(
|
|
||||||
private val logsManager: InstallLogManager,
|
|
||||||
private val application: Application,
|
|
||||||
) : ScreenModel {
|
|
||||||
/**
|
|
||||||
* All the loaded log entries sorted descending by creation date.
|
|
||||||
*/
|
|
||||||
val logEntries = mutableStateListOf<LogEntry>()
|
|
||||||
|
|
||||||
init {
|
|
||||||
loadLogsList()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun deleteLogs() = screenModelScope.launchIO {
|
|
||||||
logsManager.deleteAllEntries()
|
|
||||||
|
|
||||||
mainThread {
|
|
||||||
logEntries.clear()
|
|
||||||
application.showToast(R.string.logs_status_delete_success)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadLogsList() = screenModelScope.launchIO {
|
|
||||||
for (installId in logsManager.fetchInstallDataEntries()) {
|
|
||||||
val data = logsManager.fetchInstallData(id = installId)
|
|
||||||
?: continue
|
|
||||||
|
|
||||||
val entry = LogEntry(
|
|
||||||
id = data.id,
|
|
||||||
isError = data.isError,
|
|
||||||
installDate = DateUtils.getRelativeDateTimeString(
|
|
||||||
/* c = */ application,
|
|
||||||
/* time = */ data.installDate.toEpochMilliseconds(),
|
|
||||||
/* minResolution = */ DateUtils.SECOND_IN_MILLIS,
|
|
||||||
/* transitionResolution = */ DateUtils.WEEK_IN_MILLIS,
|
|
||||||
/* flags = */ DateUtils.FORMAT_ABBREV_ALL,
|
|
||||||
).toString(),
|
|
||||||
durationSecs = data.installDuration.inWholeMilliseconds / 1000f,
|
|
||||||
stacktracePreview = data.errorStacktrace
|
|
||||||
?.splitToSequence('\n')
|
|
||||||
?.take(3)
|
|
||||||
?.toImmutableList(),
|
|
||||||
)
|
|
||||||
|
|
||||||
mainThread { logEntries += entry }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
data class LogEntry(
|
|
||||||
val id: String,
|
|
||||||
val isError: Boolean,
|
|
||||||
val installDate: String,
|
|
||||||
val durationSecs: Float,
|
|
||||||
val stacktracePreview: ImmutableList<String>?,
|
|
||||||
)
|
|
||||||
-122
@@ -1,122 +0,0 @@
|
|||||||
package com.meowarex.rlmobile.ui.screens.logs.components
|
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material3.*
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.key
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.*
|
|
||||||
import androidx.compose.ui.geometry.Offset
|
|
||||||
import androidx.compose.ui.geometry.Size
|
|
||||||
import androidx.compose.ui.graphics.*
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import com.meowarex.rlmobile.R
|
|
||||||
import com.meowarex.rlmobile.patcher.steps.base.StepState
|
|
||||||
import com.meowarex.rlmobile.ui.screens.logs.LogEntry
|
|
||||||
import com.meowarex.rlmobile.ui.screens.patching.components.StepStateIcon
|
|
||||||
import com.meowarex.rlmobile.ui.screens.patching.components.TimeElapsed
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun LogEntryCard(
|
|
||||||
data: LogEntry,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
val errorColor = MaterialTheme.colorScheme.error
|
|
||||||
|
|
||||||
ElevatedCard(
|
|
||||||
shape = RectangleShape,
|
|
||||||
modifier = modifier
|
|
||||||
.clickable(onClick = onClick)
|
|
||||||
.clip(RoundedCornerShape(topStart = 6.dp, 12.0.dp, bottomStart = 6.dp, bottomEnd = 12.0.dp))
|
|
||||||
.drawWithCache {
|
|
||||||
val color = when (data.isError) {
|
|
||||||
true -> errorColor
|
|
||||||
false -> Color(0xFF59B463)
|
|
||||||
}
|
|
||||||
|
|
||||||
onDrawWithContent {
|
|
||||||
drawContent()
|
|
||||||
drawRect(
|
|
||||||
color = color,
|
|
||||||
alpha = .8f,
|
|
||||||
topLeft = Offset.Zero,
|
|
||||||
size = Size(4.dp.toPx(), size.height),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(14.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(vertical = 18.dp, horizontal = 22.dp),
|
|
||||||
) {
|
|
||||||
StepStateIcon(
|
|
||||||
state = if (data.isError) StepState.Error else StepState.Success,
|
|
||||||
size = 24.dp,
|
|
||||||
)
|
|
||||||
|
|
||||||
Column {
|
|
||||||
Text(
|
|
||||||
text = when (data.isError) {
|
|
||||||
true -> stringResource(R.string.status_failed)
|
|
||||||
false -> stringResource(R.string.status_success)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = data.installDate,
|
|
||||||
style = MaterialTheme.typography.labelSmall,
|
|
||||||
modifier = Modifier.alpha(.6f),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(Modifier.weight(1f, fill = true))
|
|
||||||
|
|
||||||
TimeElapsed(
|
|
||||||
seconds = data.durationSecs,
|
|
||||||
modifier = Modifier.alpha(.9f),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.stacktracePreview != null) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(start = 26.dp, end = 20.dp, bottom = 18.dp)
|
|
||||||
// https://stackoverflow.com/a/76270310/13964629
|
|
||||||
.graphicsLayer(
|
|
||||||
alpha = .95f,
|
|
||||||
compositingStrategy = CompositingStrategy.Offscreen,
|
|
||||||
)
|
|
||||||
.drawWithContent {
|
|
||||||
val colors = listOf(Color.Black, Color.Black, Color.Transparent)
|
|
||||||
drawContent()
|
|
||||||
drawRect(
|
|
||||||
brush = Brush.verticalGradient(colors),
|
|
||||||
blendMode = BlendMode.DstIn,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
// The stacktrace is separated into multiple Text elements because ellipsis is not supported per-line
|
|
||||||
for (line in data.stacktracePreview) key(line) {
|
|
||||||
Text(
|
|
||||||
text = line,
|
|
||||||
softWrap = false,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
lineHeight = 18.sp,
|
|
||||||
style = MaterialTheme.typography.labelSmall,
|
|
||||||
fontFamily = FontFamily.Companion.Monospace,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-37
@@ -1,37 +0,0 @@
|
|||||||
package com.meowarex.rlmobile.ui.screens.logs.components
|
|
||||||
|
|
||||||
import androidx.compose.material3.*
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
|
||||||
import com.meowarex.rlmobile.R
|
|
||||||
import com.meowarex.rlmobile.ui.components.BackButton
|
|
||||||
import com.meowarex.rlmobile.ui.screens.settings.SettingsScreen
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun LogsListAppBar(
|
|
||||||
onDeleteLogs: () -> Unit,
|
|
||||||
) {
|
|
||||||
TopAppBar(
|
|
||||||
navigationIcon = { BackButton() },
|
|
||||||
title = { Text(stringResource(R.string.logs_title)) },
|
|
||||||
actions = {
|
|
||||||
val navigator = LocalNavigator.current
|
|
||||||
|
|
||||||
IconButton(onClick = onDeleteLogs) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(R.drawable.ic_delete_forever),
|
|
||||||
contentDescription = stringResource(R.string.logs_action_delete_all)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
IconButton(onClick = { navigator?.push(SettingsScreen()) }) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(R.drawable.ic_settings),
|
|
||||||
contentDescription = stringResource(R.string.navigation_settings)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
-33
@@ -1,33 +0,0 @@
|
|||||||
package com.meowarex.rlmobile.ui.screens.logs.components
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.material3.*
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.alpha
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import com.meowarex.rlmobile.R
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun LogsNone(modifier: Modifier = Modifier) {
|
|
||||||
Box(modifier = modifier) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.align(Alignment.Center),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(R.drawable.ic_reciept_off),
|
|
||||||
contentDescription = null,
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.logs_none),
|
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
|
||||||
modifier = Modifier.alpha(.8f),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-53
@@ -1,53 +0,0 @@
|
|||||||
package com.meowarex.rlmobile.ui.screens.logs.components.dialogs
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.material3.*
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import com.meowarex.rlmobile.R
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun DeleteLogsDialog(
|
|
||||||
onConfirm: () -> Unit,
|
|
||||||
onDismiss: () -> Unit,
|
|
||||||
) {
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = onDismiss,
|
|
||||||
icon = {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(R.drawable.ic_delete_forever),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(32.dp),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
title = { Text(stringResource(R.string.logs_wipe_title)) },
|
|
||||||
text = {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.logs_wipe_desc),
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
Button(
|
|
||||||
onClick = onConfirm,
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.action_confirm))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dismissButton = {
|
|
||||||
Button(
|
|
||||||
onClick = onDismiss,
|
|
||||||
colors = ButtonDefaults.buttonColors(
|
|
||||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
|
||||||
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.action_cancel))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
-14
@@ -28,7 +28,6 @@ import com.meowarex.rlmobile.patcher.steps.StepGroup
|
|||||||
import com.meowarex.rlmobile.ui.components.MainActionButton
|
import com.meowarex.rlmobile.ui.components.MainActionButton
|
||||||
import com.meowarex.rlmobile.ui.components.Wakelock
|
import com.meowarex.rlmobile.ui.components.Wakelock
|
||||||
import com.meowarex.rlmobile.ui.components.dialogs.InstallerAbortDialog
|
import com.meowarex.rlmobile.ui.components.dialogs.InstallerAbortDialog
|
||||||
import com.meowarex.rlmobile.ui.components.dialogs.NetworkWarningDialog
|
|
||||||
import com.meowarex.rlmobile.ui.screens.log.LogScreen
|
import com.meowarex.rlmobile.ui.screens.log.LogScreen
|
||||||
import com.meowarex.rlmobile.ui.screens.patching.components.*
|
import com.meowarex.rlmobile.ui.screens.patching.components.*
|
||||||
import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptions
|
import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptions
|
||||||
@@ -111,19 +110,6 @@ class PatchingScreen(
|
|||||||
listState.animateScrollToItem(0)
|
listState.animateScrollToItem(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (model.showNetworkWarningDialog) {
|
|
||||||
NetworkWarningDialog(
|
|
||||||
onConfirm = { neverShow ->
|
|
||||||
model.hideNetworkWarning(neverShow)
|
|
||||||
model.install()
|
|
||||||
},
|
|
||||||
onDismiss = { neverShow ->
|
|
||||||
model.hideNetworkWarning(neverShow)
|
|
||||||
navigator.pop()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showAbortWarning) {
|
if (showAbortWarning) {
|
||||||
InstallerAbortDialog(
|
InstallerAbortDialog(
|
||||||
onDismiss = { showAbortWarning = false },
|
onDismiss = { showAbortWarning = false },
|
||||||
|
|||||||
+1
-17
@@ -41,9 +41,6 @@ class PatchingScreenModel(
|
|||||||
|
|
||||||
val devMode get() = prefs.devMode
|
val devMode get() = prefs.devMode
|
||||||
|
|
||||||
var showNetworkWarningDialog by mutableStateOf(!alreadyShownNetworkWarning && application.isNetworkDangerous())
|
|
||||||
private set
|
|
||||||
|
|
||||||
var steps by mutableStateOf<ImmutableMap<StepGroup, ImmutableList<Step>>?>(null)
|
var steps by mutableStateOf<ImmutableMap<StepGroup, ImmutableList<Step>>?>(null)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
@@ -52,11 +49,7 @@ class PatchingScreenModel(
|
|||||||
private set
|
private set
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (!prefs.showNetworkWarning)
|
install()
|
||||||
showNetworkWarningDialog = false
|
|
||||||
|
|
||||||
if (!showNetworkWarningDialog)
|
|
||||||
install()
|
|
||||||
|
|
||||||
// Rotate fun facts every so often
|
// Rotate fun facts every so often
|
||||||
screenModelScope.launch {
|
screenModelScope.launch {
|
||||||
@@ -67,12 +60,6 @@ class PatchingScreenModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hideNetworkWarning(neverShow: Boolean) {
|
|
||||||
showNetworkWarningDialog = false
|
|
||||||
alreadyShownNetworkWarning = true
|
|
||||||
prefs.showNetworkWarning = !neverShow
|
|
||||||
}
|
|
||||||
|
|
||||||
fun launchApp() {
|
fun launchApp() {
|
||||||
if (state.value !is PatchingScreenState.Success)
|
if (state.value !is PatchingScreenState.Success)
|
||||||
return
|
return
|
||||||
@@ -194,9 +181,6 @@ class PatchingScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
// Global state to avoid showing the warning more than once per launch
|
|
||||||
private var alreadyShownNetworkWarning = false
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Random fun facts to show on the installation screen.
|
* Random fun facts to show on the installation screen.
|
||||||
*/
|
*/
|
||||||
|
|||||||
+192
-4
@@ -2,70 +2,258 @@ package com.meowarex.rlmobile.ui.screens.patchopts
|
|||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import com.meowarex.rlmobile.R
|
import com.meowarex.rlmobile.R
|
||||||
|
import com.meowarex.rlmobile.ui.screens.patchopts.PatchDefault.Disabled
|
||||||
|
import com.meowarex.rlmobile.ui.screens.patchopts.PatchDefault.Enabled
|
||||||
|
|
||||||
|
data class PatchVariant(
|
||||||
|
@StringRes val titleRes: Int,
|
||||||
|
val fileNames: List<String>,
|
||||||
|
val extensionFiles: List<String> = emptyList(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
enum class KnownPatch(
|
enum class KnownPatch(
|
||||||
|
val order: Int, // Patch order in the UI List (lower = higher up) [Main Patches: multiples of 10 | Sub Patches: multiples of 1]
|
||||||
val fileNames: List<String>,
|
val fileNames: List<String>,
|
||||||
|
val extensionFiles: List<String> = emptyList(),
|
||||||
@StringRes val titleRes: Int,
|
@StringRes val titleRes: Int,
|
||||||
@StringRes val descRes: Int,
|
@StringRes val descRes: Int,
|
||||||
|
val default: PatchDefault, // Default state of the patch in the UI List (enabled/disabled)
|
||||||
val requires: List<KnownPatch> = emptyList(),
|
val requires: List<KnownPatch> = emptyList(),
|
||||||
val disables: List<KnownPatch> = emptyList(),
|
val disables: List<KnownPatch> = emptyList(),
|
||||||
|
val variants: List<PatchVariant> = emptyList(),
|
||||||
|
val defaultVariantIndex: Int = 0,
|
||||||
|
val advancedOptions: List<PatchOption> = emptyList(),
|
||||||
) {
|
) {
|
||||||
// Dependency-first order (later refs need backward resolution)
|
|
||||||
LyricsDisableCover(
|
LyricsDisableCover(
|
||||||
|
order = 41,
|
||||||
fileNames = listOf("lyrics-disable-cover.patch"),
|
fileNames = listOf("lyrics-disable-cover.patch"),
|
||||||
titleRes = R.string.patch_lyrics_disable_cover_title,
|
titleRes = R.string.patch_lyrics_disable_cover_title,
|
||||||
descRes = R.string.patch_lyrics_disable_cover_desc,
|
descRes = R.string.patch_lyrics_disable_cover_desc,
|
||||||
|
default = Enabled,
|
||||||
),
|
),
|
||||||
LyricsReplaceLyricsButton(
|
LyricsReplaceLyricsButton(
|
||||||
|
order = 42,
|
||||||
fileNames = listOf(
|
fileNames = listOf(
|
||||||
"lyrics-replace-lyrics-button.patch",
|
"lyrics-replace-lyrics-button.patch",
|
||||||
"lyrics-sparkle-conditional-visibility.patch",
|
"lyrics-sparkle-conditional-visibility.patch",
|
||||||
),
|
),
|
||||||
titleRes = R.string.patch_lyrics_replace_button_title,
|
titleRes = R.string.patch_lyrics_replace_button_title,
|
||||||
descRes = R.string.patch_lyrics_replace_button_desc,
|
descRes = R.string.patch_lyrics_replace_button_desc,
|
||||||
|
default = Enabled,
|
||||||
),
|
),
|
||||||
LyricsReplaceShareButton(
|
LyricsReplaceShareButton(
|
||||||
|
order = 43,
|
||||||
fileNames = listOf("lyrics-replace-share-button.patch"),
|
fileNames = listOf("lyrics-replace-share-button.patch"),
|
||||||
titleRes = R.string.patch_lyrics_replace_share_button_title,
|
titleRes = R.string.patch_lyrics_replace_share_button_title,
|
||||||
descRes = R.string.patch_lyrics_replace_share_button_desc,
|
descRes = R.string.patch_lyrics_replace_share_button_desc,
|
||||||
|
default = Enabled,
|
||||||
|
),
|
||||||
|
LyricsRlApi(
|
||||||
|
order = 20,
|
||||||
|
fileNames = listOf(
|
||||||
|
"lyrics-rl-api.patch",
|
||||||
|
"lyrics-rl-api-observer.patch",
|
||||||
|
),
|
||||||
|
titleRes = R.string.patch_lyrics_rl_api_title,
|
||||||
|
descRes = R.string.patch_lyrics_rl_api_desc,
|
||||||
|
default = Disabled,
|
||||||
|
),
|
||||||
|
LyricsKeepControlsVisible(
|
||||||
|
order = 60,
|
||||||
|
fileNames = listOf("lyrics-keep-controls-visible.patch"),
|
||||||
|
titleRes = R.string.patch_lyrics_keep_controls_title,
|
||||||
|
descRes = R.string.patch_lyrics_keep_controls_desc,
|
||||||
|
default = Enabled,
|
||||||
),
|
),
|
||||||
PlayerBackdrop(
|
PlayerBackdrop(
|
||||||
|
order = 30,
|
||||||
fileNames = listOf("player-backdrop.patch"),
|
fileNames = listOf("player-backdrop.patch"),
|
||||||
titleRes = R.string.patch_player_backdrop_title,
|
titleRes = R.string.patch_player_backdrop_title,
|
||||||
descRes = R.string.patch_player_backdrop_desc,
|
descRes = R.string.patch_player_backdrop_desc,
|
||||||
|
default = Enabled,
|
||||||
|
advancedOptions = listOf(
|
||||||
|
PatchOption.Slider(
|
||||||
|
key = "blur_strength",
|
||||||
|
titleRes = R.string.patch_opt_backdrop_blur_title,
|
||||||
|
descRes = R.string.patch_opt_backdrop_blur_desc,
|
||||||
|
default = 50f,
|
||||||
|
valueRange = 0f..100f,
|
||||||
|
steps = 19, // dots every 5% (0,5,…,100); snap only when locked
|
||||||
|
displayAsPercent = true,
|
||||||
|
token = "RL_BLUR_BITS",
|
||||||
|
encode = SmaliEncode(EncodeKind.FloatBits, scale = 1.8f),
|
||||||
|
),
|
||||||
|
PatchOption.Slider(
|
||||||
|
key = "dimming",
|
||||||
|
titleRes = R.string.patch_opt_backdrop_dimming_title,
|
||||||
|
descRes = R.string.patch_opt_backdrop_dimming_desc,
|
||||||
|
default = 50f, // 50% == the original -0x80000000
|
||||||
|
valueRange = 0f..100f,
|
||||||
|
steps = 19, // dots every 5%
|
||||||
|
displayAsPercent = true,
|
||||||
|
token = "RL_SCRIM_ARGB",
|
||||||
|
encode = SmaliEncode(EncodeKind.ArgbAlpha),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
QualityBadgeColors(
|
||||||
|
order = 36,
|
||||||
|
fileNames = listOf("player-quality-badge-colors.patch"),
|
||||||
|
titleRes = R.string.patch_quality_badge_colors_title,
|
||||||
|
descRes = R.string.patch_quality_badge_colors_desc,
|
||||||
|
default = Enabled,
|
||||||
|
),
|
||||||
|
PlayerOneHanded(
|
||||||
|
order = 37,
|
||||||
|
fileNames = listOf("player-one-handed.patch"),
|
||||||
|
titleRes = R.string.patch_player_one_handed_title,
|
||||||
|
descRes = R.string.patch_player_one_handed_desc,
|
||||||
|
default = Disabled,
|
||||||
|
),
|
||||||
|
CoverEverywhere(
|
||||||
|
order = 35,
|
||||||
|
fileNames = listOf(
|
||||||
|
"home-backdrop.patch",
|
||||||
|
"collection-backdrop.patch",
|
||||||
|
"cover-capture.patch",
|
||||||
|
),
|
||||||
|
titleRes = R.string.patch_cover_everywhere_title,
|
||||||
|
descRes = R.string.patch_cover_everywhere_desc,
|
||||||
|
default = Disabled,
|
||||||
),
|
),
|
||||||
DebugMenuUnlock(
|
DebugMenuUnlock(
|
||||||
|
order = 100,
|
||||||
fileNames = listOf("debug-menu-unlock.patch"),
|
fileNames = listOf("debug-menu-unlock.patch"),
|
||||||
titleRes = R.string.patch_debug_menu_unlock_title,
|
titleRes = R.string.patch_debug_menu_unlock_title,
|
||||||
descRes = R.string.patch_debug_menu_unlock_desc,
|
descRes = R.string.patch_debug_menu_unlock_desc,
|
||||||
|
default = Disabled,
|
||||||
),
|
),
|
||||||
LyricsProgressPill(
|
LyricsProgressPill(
|
||||||
|
order = 40,
|
||||||
fileNames = listOf(
|
fileNames = listOf(
|
||||||
"lyrics-progress-pill.patch",
|
"lyrics-progress-pill.patch",
|
||||||
"lyrics-fade-region.patch",
|
"lyrics-fade-region.patch",
|
||||||
),
|
),
|
||||||
titleRes = R.string.patch_lyrics_progress_pill_title,
|
titleRes = R.string.patch_lyrics_progress_pill_title,
|
||||||
descRes = R.string.patch_lyrics_progress_pill_desc,
|
descRes = R.string.patch_lyrics_progress_pill_desc,
|
||||||
requires = listOf(LyricsDisableCover, LyricsReplaceShareButton),
|
default = Enabled,
|
||||||
|
requires = listOf(LyricsDisableCover, LyricsReplaceLyricsButton, LyricsReplaceShareButton),
|
||||||
|
),
|
||||||
|
MiniPlayerRedesign(
|
||||||
|
order = 50,
|
||||||
|
fileNames = emptyList(),
|
||||||
|
titleRes = R.string.patch_mini_player_redesign_title,
|
||||||
|
descRes = R.string.patch_mini_player_redesign_desc,
|
||||||
|
default = Disabled,
|
||||||
|
defaultVariantIndex = 2,
|
||||||
|
variants = listOf(
|
||||||
|
PatchVariant( // 0: Floating — stock rounded pill
|
||||||
|
titleRes = R.string.patch_mini_player_variant_floating_title,
|
||||||
|
fileNames = emptyList(),
|
||||||
|
),
|
||||||
|
PatchVariant( // 1: Grey — square
|
||||||
|
titleRes = R.string.patch_mini_player_variant_square_grey_title,
|
||||||
|
fileNames = listOf("mini-player-grey.patch"),
|
||||||
|
),
|
||||||
|
PatchVariant( // 2: Black — square black background
|
||||||
|
titleRes = R.string.patch_mini_player_variant_square_black_title,
|
||||||
|
fileNames = listOf("mini-player-black.patch"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
advancedOptions = listOf(
|
||||||
|
PatchOption.Toggle(
|
||||||
|
key = "dynamic_bg",
|
||||||
|
titleRes = R.string.patch_mini_player_dynamic_bg_title,
|
||||||
|
descRes = R.string.patch_mini_player_dynamic_bg_desc,
|
||||||
|
default = false,
|
||||||
|
inline = true,
|
||||||
|
fileNames = listOf("mini-player-dynamic-bg.patch"),
|
||||||
|
extensionFiles = listOf("radiant/MiniPlayerBackground.smali"),
|
||||||
|
hidesVariants = listOf(2),
|
||||||
|
relabelVariants = mapOf(1 to R.string.patch_mini_player_variant_legacy_title),
|
||||||
|
),
|
||||||
|
// Animated progress border around the floating pill
|
||||||
|
PatchOption.Toggle(
|
||||||
|
key = "border",
|
||||||
|
titleRes = R.string.patch_mini_player_border_title,
|
||||||
|
descRes = R.string.patch_mini_player_border_desc,
|
||||||
|
default = false,
|
||||||
|
requiresVariant = 0,
|
||||||
|
fileNames = listOf("mini-player-floating-border.patch"),
|
||||||
|
extensionFiles = listOf("radiant/MiniSeekerFloating.smali"),
|
||||||
|
),
|
||||||
|
// Swipe up to open the full player
|
||||||
|
PatchOption.Toggle(
|
||||||
|
key = "gestures",
|
||||||
|
titleRes = R.string.patch_mini_player_gestures_title,
|
||||||
|
descRes = R.string.patch_mini_player_gestures_desc,
|
||||||
|
default = true,
|
||||||
|
fileNames = listOf("mini-player-gestures.patch"),
|
||||||
|
extensionFiles = listOf(
|
||||||
|
"radiant/MiniPlayerGestures.smali",
|
||||||
|
"radiant/MiniPlayerGestures\$Gesture.smali",
|
||||||
|
"radiant/MiniPlayerGestures\$FeedbackLayer.smali",
|
||||||
|
"radiant/MiniPlayerGestures\$FeedbackResetAnimator.smali",
|
||||||
|
"radiant/MiniPlayerGestures\$RootGesture.smali",
|
||||||
|
"radiant/MiniPlayerGestures\$ApplyPending.smali",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
PatchOption.Choice(
|
||||||
|
key = "swipe_up_drag",
|
||||||
|
titleRes = R.string.patch_mini_player_drag_title,
|
||||||
|
descRes = R.string.patch_mini_player_drag_desc,
|
||||||
|
entries = listOf(
|
||||||
|
ChoiceEntry(R.string.patch_mini_player_drag_static, value = "0x0"),
|
||||||
|
ChoiceEntry(R.string.patch_mini_player_drag_drag, value = "0x1"),
|
||||||
|
),
|
||||||
|
defaultIndex = 1,
|
||||||
|
requiresOption = "gestures",
|
||||||
|
token = "RL_MINI_PLAYER_SWIPE_UP_DRAG",
|
||||||
|
),
|
||||||
|
// Swipe left/right to skip
|
||||||
|
PatchOption.Toggle(
|
||||||
|
key = "next_prev",
|
||||||
|
titleRes = R.string.patch_mini_player_left_right_gestures_title,
|
||||||
|
descRes = R.string.patch_mini_player_left_right_gestures_desc,
|
||||||
|
default = false,
|
||||||
|
requiresOption = "gestures",
|
||||||
|
fileNames = listOf("mini-player-gestures-left-right.patch"),
|
||||||
|
extensionFiles = listOf(
|
||||||
|
"radiant/MiniPlayerTrackGestures.smali",
|
||||||
|
"radiant/MiniPlayerTrackGestures\$Gesture.smali",
|
||||||
|
"radiant/MiniPlayerTrackGestures\$OffsetLayer.smali",
|
||||||
|
"radiant/MiniPlayerTrackGestures\$ResetAnimator.smali",
|
||||||
|
"radiant/MiniPlayerTrackGestures\$TextDraw.smali",
|
||||||
|
"com/tidal/android/feature/appscaffold/ui/q\$c.smali",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
EnableLegacyUi(
|
EnableLegacyUi(
|
||||||
|
order = 10,
|
||||||
fileNames = listOf("enable-legacy-ui.patch"),
|
fileNames = listOf("enable-legacy-ui.patch"),
|
||||||
titleRes = R.string.patch_enable_legacy_ui_title,
|
titleRes = R.string.patch_enable_legacy_ui_title,
|
||||||
descRes = R.string.patch_enable_legacy_ui_desc,
|
descRes = R.string.patch_enable_legacy_ui_desc,
|
||||||
|
default = Disabled,
|
||||||
requires = listOf(DebugMenuUnlock),
|
requires = listOf(DebugMenuUnlock),
|
||||||
disables = listOf(
|
disables = listOf(
|
||||||
LyricsDisableCover,
|
LyricsDisableCover,
|
||||||
LyricsReplaceLyricsButton,
|
LyricsReplaceLyricsButton,
|
||||||
LyricsReplaceShareButton,
|
LyricsReplaceShareButton,
|
||||||
|
LyricsRlApi,
|
||||||
|
LyricsKeepControlsVisible,
|
||||||
PlayerBackdrop,
|
PlayerBackdrop,
|
||||||
|
QualityBadgeColors,
|
||||||
LyricsProgressPill,
|
LyricsProgressPill,
|
||||||
|
CoverEverywhere,
|
||||||
|
MiniPlayerRedesign,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
// Alphabetical by first filename, but pin DebugMenuUnlock to the bottom
|
|
||||||
val All: List<KnownPatch> = entries.sortedWith(
|
val All: List<KnownPatch> = entries.sortedWith(
|
||||||
compareBy({ it == DebugMenuUnlock }, { it.fileNames.first() })
|
compareBy({ it.order }, { it.fileNames.firstOrNull() ?: it.name })
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.meowarex.rlmobile.ui.screens.patchopts
|
||||||
|
|
||||||
|
enum class PatchDefault(val isEnabled: Boolean) {
|
||||||
|
Enabled(true),
|
||||||
|
Disabled(false),
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package com.meowarex.rlmobile.ui.screens.patchopts
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
|
||||||
|
sealed interface PatchOption {
|
||||||
|
/** Stable identifier, unique within a single patch. */
|
||||||
|
val key: String
|
||||||
|
|
||||||
|
@get:StringRes
|
||||||
|
val titleRes: Int
|
||||||
|
|
||||||
|
@get:StringRes
|
||||||
|
val descRes: Int
|
||||||
|
|
||||||
|
/** A simple on/off switch. */
|
||||||
|
data class Toggle(
|
||||||
|
override val key: String,
|
||||||
|
@StringRes override val titleRes: Int,
|
||||||
|
@StringRes override val descRes: Int,
|
||||||
|
val default: Boolean,
|
||||||
|
/** Patch files applied only while this toggle is on (and its patch is enabled). */
|
||||||
|
val fileNames: List<String> = emptyList(),
|
||||||
|
/** Helper smali extracted only while this toggle is on. */
|
||||||
|
val extensionFiles: List<String> = emptyList(),
|
||||||
|
/** Render inline beneath the variant selector instead of inside the advanced sheet. */
|
||||||
|
val inline: Boolean = false,
|
||||||
|
/** Greyed out (with the lock dialog) unless this variant index is the selected one. */
|
||||||
|
val requiresVariant: Int? = null,
|
||||||
|
/** Greyed out unless the sibling option with this key is currently on. */
|
||||||
|
val requiresOption: String? = null,
|
||||||
|
/** Variant indices hidden from the picker while this toggle is on. */
|
||||||
|
val hidesVariants: List<Int> = emptyList(),
|
||||||
|
/** Variant title overrides (index -> @StringRes) applied while this toggle is on. */
|
||||||
|
val relabelVariants: Map<Int, Int> = emptyMap(),
|
||||||
|
/** Placeholder name (without the surrounding `__`) baked into the `.patch` files. */
|
||||||
|
val token: String? = null,
|
||||||
|
) : PatchOption
|
||||||
|
|
||||||
|
/** A continuous (or stepped) numeric value within [valueRange]. */
|
||||||
|
data class Slider(
|
||||||
|
override val key: String,
|
||||||
|
@StringRes override val titleRes: Int,
|
||||||
|
@StringRes override val descRes: Int,
|
||||||
|
val default: Float,
|
||||||
|
val valueRange: ClosedFloatingPointRange<Float>,
|
||||||
|
/** Number of discrete steps between the range ends (0 = continuous). */
|
||||||
|
val steps: Int = 0,
|
||||||
|
/** Render the value as a rounded percentage (e.g. "50%"). */
|
||||||
|
val displayAsPercent: Boolean = false,
|
||||||
|
/** Optional unit suffix shown after the value (e.g. "dp"). */
|
||||||
|
@StringRes val unitRes: Int? = null,
|
||||||
|
/** Placeholder name (without the surrounding `__`) baked into the `.patch` files. */
|
||||||
|
val token: String? = null,
|
||||||
|
/** How the chosen value becomes the smali literal that replaces the [token]. */
|
||||||
|
val encode: SmaliEncode? = null,
|
||||||
|
) : PatchOption
|
||||||
|
|
||||||
|
/** A single choice out of a small list, rendered as a segmented control. */
|
||||||
|
data class Choice(
|
||||||
|
override val key: String,
|
||||||
|
@StringRes override val titleRes: Int,
|
||||||
|
@StringRes override val descRes: Int,
|
||||||
|
val entries: List<ChoiceEntry>,
|
||||||
|
val defaultIndex: Int = 0,
|
||||||
|
val requiresOption: String? = null,
|
||||||
|
val token: String? = null,
|
||||||
|
) : PatchOption
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ChoiceEntry(
|
||||||
|
@StringRes val labelRes: Int,
|
||||||
|
val value: String? = null,
|
||||||
|
)
|
||||||
+109
-9
@@ -25,26 +25,126 @@ 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.
|
||||||
*/
|
*/
|
||||||
val customPatches: PatchComponent? = null,
|
val customPatches: PatchComponent? = null,
|
||||||
|
|
||||||
val disabledPatches: Set<String> = emptySet(),
|
val patchStates: Map<String, Boolean> = emptyMap(),
|
||||||
|
|
||||||
|
val selectedVariants: Map<String, Int> = emptyMap(),
|
||||||
|
val optionFloats: Map<String, Float> = emptyMap(),
|
||||||
|
val optionBools: Map<String, Boolean> = emptyMap(),
|
||||||
|
val optionInts: Map<String, Int> = emptyMap(),
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
|
fun isEnabled(spec: PatchSpec): Boolean =
|
||||||
|
patchStates[spec.id] ?: spec.defaultEnabled
|
||||||
|
|
||||||
|
fun variantIndex(spec: PatchSpec): Int {
|
||||||
|
val stored = (selectedVariants[spec.id] ?: spec.defaultVariantIndex)
|
||||||
|
.coerceIn(0, spec.variants.lastIndex.coerceAtLeast(0))
|
||||||
|
return spec.resolveVariantIndex(stored) { isToggleOn(spec, it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sliderValue(spec: PatchSpec, option: OptionSpec.Slider): Float =
|
||||||
|
(optionFloats["${spec.id}/${option.key}"] ?: option.default).coerceIn(option.min, option.max)
|
||||||
|
|
||||||
|
fun choiceIndex(spec: PatchSpec, option: OptionSpec.Choice): Int =
|
||||||
|
(optionInts["${spec.id}/${option.key}"] ?: option.defaultIndex)
|
||||||
|
.coerceIn(0, option.entries.lastIndex.coerceAtLeast(0))
|
||||||
|
|
||||||
|
fun isToggleOn(spec: PatchSpec, option: OptionSpec.Toggle): Boolean =
|
||||||
|
optionBools["${spec.id}/${option.key}"] ?: option.default
|
||||||
|
|
||||||
|
fun isToggleActive(spec: PatchSpec, option: OptionSpec.Toggle): Boolean {
|
||||||
|
if (!isToggleOn(spec, option)) return false
|
||||||
|
option.requiresVariant?.let { if (variantIndex(spec) != it) return false }
|
||||||
|
option.requiresOption?.let { key ->
|
||||||
|
val required = spec.advancedOptions.filterIsInstance<OptionSpec.Toggle>()
|
||||||
|
.firstOrNull { it.key == key }
|
||||||
|
if (required != null && !isToggleOn(spec, required)) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun disabledPatchFiles(specs: List<PatchSpec>): Set<String> = buildSet {
|
||||||
|
for (spec in specs) {
|
||||||
|
val enabled = isEnabled(spec)
|
||||||
|
if (spec.variants.isEmpty()) {
|
||||||
|
if (!enabled) addAll(spec.fileNames)
|
||||||
|
} else {
|
||||||
|
val selected = variantIndex(spec)
|
||||||
|
spec.variants.forEachIndexed { index, variant ->
|
||||||
|
if (!enabled || index != selected) addAll(variant.fileNames)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Toggle sub-options that gate patch files.
|
||||||
|
for (option in spec.advancedOptions) {
|
||||||
|
if (option is OptionSpec.Toggle && option.fileNames.isNotEmpty()) {
|
||||||
|
if (!enabled || !isToggleActive(spec, option)) addAll(option.fileNames)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun knownExtensionFiles(specs: List<PatchSpec>): Set<String> = buildSet {
|
||||||
|
for (spec in specs) {
|
||||||
|
addAll(spec.extensionFiles)
|
||||||
|
spec.variants.forEach { addAll(it.extensionFiles) }
|
||||||
|
spec.advancedOptions.forEach { if (it is OptionSpec.Toggle) addAll(it.extensionFiles) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun enabledExtensionFiles(specs: List<PatchSpec>): Set<String> = buildSet {
|
||||||
|
for (spec in specs) {
|
||||||
|
if (!isEnabled(spec)) continue
|
||||||
|
addAll(spec.extensionFiles)
|
||||||
|
if (spec.variants.isNotEmpty()) {
|
||||||
|
spec.variants.getOrNull(variantIndex(spec))?.let { addAll(it.extensionFiles) }
|
||||||
|
}
|
||||||
|
for (option in spec.advancedOptions) {
|
||||||
|
if (option is OptionSpec.Toggle && isToggleActive(spec, option)) addAll(option.extensionFiles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun smaliSubstitutions(specs: List<PatchSpec>): Map<String, String> = buildMap {
|
||||||
|
for (spec in specs) {
|
||||||
|
if (!isEnabled(spec)) continue
|
||||||
|
for (option in spec.advancedOptions) {
|
||||||
|
when (option) {
|
||||||
|
is OptionSpec.Toggle -> {
|
||||||
|
val token = option.token ?: continue
|
||||||
|
put("__${token}__", if (isToggleActive(spec, option)) "0x1" else "0x0")
|
||||||
|
}
|
||||||
|
is OptionSpec.Slider -> {
|
||||||
|
val token = option.token ?: continue
|
||||||
|
val encode = option.encode ?: continue
|
||||||
|
put("__${token}__", encode.encode(sliderValue(spec, option)))
|
||||||
|
}
|
||||||
|
is OptionSpec.Choice -> {
|
||||||
|
val token = option.token ?: continue
|
||||||
|
val gatedOff = option.requiresOption?.let { key ->
|
||||||
|
spec.advancedOptions.filterIsInstance<OptionSpec.Toggle>()
|
||||||
|
.firstOrNull { it.key == key }
|
||||||
|
?.let { !isToggleOn(spec, it) }
|
||||||
|
} ?: false
|
||||||
|
val index = if (gatedOff) 0 else choiceIndex(spec, option)
|
||||||
|
put("__${token}__", option.values.getOrNull(index) ?: continue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val Default = PatchOptions(
|
val Default: PatchOptions = PatchOptions(
|
||||||
appName = "TIDAL",
|
appName = "TIDAL",
|
||||||
packageName = "com.aspiro.tidal",
|
packageName = "com.aspiro.tidal",
|
||||||
debuggable = false,
|
debuggable = false,
|
||||||
customInjector = null,
|
|
||||||
customPatches = null,
|
|
||||||
disabledPatches = emptySet(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+248
-24
@@ -2,21 +2,35 @@ package com.meowarex.rlmobile.ui.screens.patchopts
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager.NameNotFoundException
|
import android.content.pm.PackageManager.NameNotFoundException
|
||||||
|
import android.util.Log
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import cafe.adriel.voyager.core.model.ScreenModel
|
import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
|
import com.github.diamondminer88.zip.ZipReader
|
||||||
|
import com.meowarex.rlmobile.BuildConfig
|
||||||
|
import com.meowarex.rlmobile.manager.PathManager
|
||||||
import com.meowarex.rlmobile.manager.PreferencesManager
|
import com.meowarex.rlmobile.manager.PreferencesManager
|
||||||
import com.meowarex.rlmobile.ui.screens.componentopts.ComponentOptionsScreen
|
import com.meowarex.rlmobile.ui.screens.componentopts.ComponentOptionsScreen
|
||||||
import com.meowarex.rlmobile.ui.screens.componentopts.PatchComponent
|
import com.meowarex.rlmobile.ui.screens.componentopts.PatchComponent
|
||||||
import com.meowarex.rlmobile.ui.util.pushForResult
|
import com.meowarex.rlmobile.ui.util.pushForResult
|
||||||
import com.meowarex.rlmobile.util.*
|
import com.meowarex.rlmobile.util.*
|
||||||
|
import com.meowarex.rlmobile.manager.download.IDownloadManager
|
||||||
|
import com.meowarex.rlmobile.manager.download.KtorDownloadManager
|
||||||
|
import com.meowarex.rlmobile.network.services.RadiantLyricsGithubService
|
||||||
|
import com.meowarex.rlmobile.network.utils.getOrThrow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
class PatchOptionsModel(
|
class PatchOptionsModel(
|
||||||
prefilledOptions: PatchOptions,
|
prefilledOptions: PatchOptions,
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val prefs: PreferencesManager,
|
private val prefs: PreferencesManager,
|
||||||
|
private val paths: PathManager,
|
||||||
|
private val json: Json,
|
||||||
|
private val github: RadiantLyricsGithubService,
|
||||||
|
private val downloader: KtorDownloadManager,
|
||||||
) : ScreenModel {
|
) : ScreenModel {
|
||||||
var packageName by mutableStateOf(prefilledOptions.packageName)
|
var packageName by mutableStateOf(prefilledOptions.packageName)
|
||||||
private set
|
private set
|
||||||
@@ -47,50 +61,164 @@ class PatchOptionsModel(
|
|||||||
debuggable = value
|
debuggable = value
|
||||||
}
|
}
|
||||||
|
|
||||||
var disabledPatches by mutableStateOf(prefilledOptions.disabledPatches)
|
// The accordion renders from [specs]. It defaults to the compiled-in (localized) list and is
|
||||||
|
// rebuilt from a custom zip's manifest.json whenever a custom patch set is selected. (Rminder for others about the new custom patch selection flow)
|
||||||
|
|
||||||
|
private val builtinSpecs: List<PatchSpec> = builtinPatchSpecs { context.getString(it) }
|
||||||
|
|
||||||
|
var specs by mutableStateOf(builtinSpecs)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
fun isPatchEnabled(patch: KnownPatch): Boolean =
|
var specsLoading by mutableStateOf(false)
|
||||||
patch.fileNames.none { it in disabledPatches }
|
private set
|
||||||
|
|
||||||
fun setPatchEnabled(patch: KnownPatch, enabled: Boolean) {
|
var patchStates by mutableStateOf(prefilledOptions.patchStates)
|
||||||
fun closure(seed: KnownPatch, step: (KnownPatch) -> List<KnownPatch>): Set<KnownPatch> =
|
private set
|
||||||
|
|
||||||
|
var selectedVariants by mutableStateOf(prefilledOptions.selectedVariants)
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun variantIndex(spec: PatchSpec): Int =
|
||||||
|
(selectedVariants[spec.id] ?: spec.defaultVariantIndex)
|
||||||
|
.coerceIn(0, spec.variants.lastIndex.coerceAtLeast(0))
|
||||||
|
|
||||||
|
fun isPatchEnabled(spec: PatchSpec): Boolean =
|
||||||
|
patchStates[spec.id] ?: spec.defaultEnabled
|
||||||
|
|
||||||
|
fun setPatchEnabled(spec: PatchSpec, enabled: Boolean) {
|
||||||
|
val byId = specs.associateBy { it.id }
|
||||||
|
fun closure(seed: PatchSpec, step: (PatchSpec) -> List<PatchSpec>): Set<PatchSpec> =
|
||||||
buildSet {
|
buildSet {
|
||||||
fun walk(p: KnownPatch) { if (add(p)) step(p).forEach(::walk) }
|
fun walk(p: PatchSpec) { if (add(p)) step(p).forEach(::walk) }
|
||||||
walk(seed)
|
walk(seed)
|
||||||
}
|
}
|
||||||
|
|
||||||
val enableUnits: Set<KnownPatch>
|
val requiresOf = { p: PatchSpec -> p.requires.mapNotNull(byId::get) }
|
||||||
val disableUnits: Set<KnownPatch>
|
val dependentsOf = { p: PatchSpec -> specs.filter { p.id in it.requires } }
|
||||||
|
|
||||||
|
val enableUnits: Set<PatchSpec>
|
||||||
|
val disableUnits: Set<PatchSpec>
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
enableUnits = closure(patch) { it.requires }
|
enableUnits = closure(spec, requiresOf)
|
||||||
disableUnits = enableUnits.flatMap { it.disables }
|
disableUnits = enableUnits.flatMap { it.disables }
|
||||||
.flatMapTo(mutableSetOf()) { d ->
|
.mapNotNull(byId::get)
|
||||||
closure(d) { dep -> KnownPatch.All.filter { dep in it.requires } }
|
.flatMapTo(mutableSetOf()) { d -> closure(d, dependentsOf) }
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
enableUnits = emptySet()
|
enableUnits = emptySet()
|
||||||
disableUnits = closure(patch) { p -> KnownPatch.All.filter { p in it.requires } }
|
disableUnits = closure(spec, dependentsOf)
|
||||||
}
|
}
|
||||||
|
|
||||||
val enableFiles = enableUnits.flatMap { it.fileNames }.toSet()
|
patchStates = patchStates.toMutableMap().apply {
|
||||||
val disableFiles = disableUnits.flatMap { it.fileNames }.toSet()
|
enableUnits.forEach { this[it.id] = true }
|
||||||
disabledPatches = (disabledPatches - enableFiles) + disableFiles
|
disableUnits.forEach { this[it.id] = false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectVariant(spec: PatchSpec, index: Int) {
|
||||||
|
if (spec.variants.isEmpty() || index !in spec.variants.indices) return
|
||||||
|
selectedVariants = selectedVariants + (spec.id to index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advanced (per-patch) options
|
||||||
|
// Values are seeded from the prefilled config, edited here, and written back out in
|
||||||
|
// generateConfig(). At patch time PatchOptions.smaliSubstitutions() turns slider values into
|
||||||
|
// the literals baked into the matching `.patch` files (see SmaliPatchStep). ()
|
||||||
|
|
||||||
|
private var optionBools by mutableStateOf(prefilledOptions.optionBools)
|
||||||
|
private var optionFloats by mutableStateOf(prefilledOptions.optionFloats)
|
||||||
|
private var optionInts by mutableStateOf(prefilledOptions.optionInts)
|
||||||
|
|
||||||
|
private fun keyOf(spec: PatchSpec, option: OptionSpec): String = "${spec.id}/${option.key}"
|
||||||
|
|
||||||
|
fun toggleValue(spec: PatchSpec, option: OptionSpec.Toggle): Boolean =
|
||||||
|
optionBools[keyOf(spec, option)] ?: option.default
|
||||||
|
|
||||||
|
fun setToggleValue(spec: PatchSpec, option: OptionSpec.Toggle, value: Boolean) {
|
||||||
|
optionBools = optionBools + (keyOf(spec, option) to value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sliderValue(spec: PatchSpec, option: OptionSpec.Slider): Float =
|
||||||
|
(optionFloats[keyOf(spec, option)] ?: option.default).coerceIn(option.min, option.max)
|
||||||
|
|
||||||
|
fun setSliderValue(spec: PatchSpec, option: OptionSpec.Slider, value: Float) {
|
||||||
|
optionFloats = optionFloats + (keyOf(spec, option) to value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun choiceValue(spec: PatchSpec, option: OptionSpec.Choice): Int =
|
||||||
|
(optionInts[keyOf(spec, option)] ?: option.defaultIndex)
|
||||||
|
.coerceIn(0, option.entries.lastIndex.coerceAtLeast(0))
|
||||||
|
|
||||||
|
fun setChoiceValue(spec: PatchSpec, option: OptionSpec.Choice, index: Int) {
|
||||||
|
if (index !in option.entries.indices) return
|
||||||
|
optionInts = optionInts + (keyOf(spec, option) to index)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isAdvancedModified(spec: PatchSpec): Boolean = spec.advancedOptions.any { option ->
|
||||||
|
when (option) {
|
||||||
|
is OptionSpec.Toggle -> toggleValue(spec, option) != option.default
|
||||||
|
is OptionSpec.Slider -> sliderValue(spec, option) != option.default
|
||||||
|
is OptionSpec.Choice -> choiceValue(spec, option) != option.defaultIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetAdvanced(spec: PatchSpec) {
|
||||||
|
val prefix = "${spec.id}/"
|
||||||
|
optionBools = optionBools.filterKeys { !it.startsWith(prefix) }
|
||||||
|
optionFloats = optionFloats.filterKeys { !it.startsWith(prefix) }
|
||||||
|
optionInts = optionInts.filterKeys { !it.startsWith(prefix) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val optionState: PatchOptionState = PatchOptionState(
|
||||||
|
toggle = ::toggleValue,
|
||||||
|
setToggle = ::setToggleValue,
|
||||||
|
slider = ::sliderValue,
|
||||||
|
setSlider = ::setSliderValue,
|
||||||
|
choice = ::choiceValue,
|
||||||
|
setChoice = ::setChoiceValue,
|
||||||
|
isModified = ::isAdvancedModified,
|
||||||
|
reset = ::resetAdvanced,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun lockState(spec: PatchSpec): PatchLock {
|
||||||
|
if (spec.variants.isNotEmpty()) return PatchLock.Free
|
||||||
|
|
||||||
|
val byId = specs.associateBy { it.id }
|
||||||
|
fun closure(seed: PatchSpec, step: (PatchSpec) -> List<PatchSpec>): Set<PatchSpec> =
|
||||||
|
buildSet {
|
||||||
|
fun walk(p: PatchSpec) { if (add(p)) step(p).forEach(::walk) }
|
||||||
|
walk(seed)
|
||||||
|
}
|
||||||
|
|
||||||
|
val requiresOf = { p: PatchSpec -> p.requires.mapNotNull(byId::get) }
|
||||||
|
val dependentsOf = { p: PatchSpec -> specs.filter { p.id in it.requires } }
|
||||||
|
|
||||||
|
for (other in specs) {
|
||||||
|
if (other.id == spec.id || !isPatchEnabled(other)) continue
|
||||||
|
|
||||||
|
val requiresClosure = closure(other, requiresOf)
|
||||||
|
if (spec in requiresClosure - other) return PatchLock.LockedOn(other)
|
||||||
|
|
||||||
|
val disablesClosure = requiresClosure.flatMap { it.disables }
|
||||||
|
.mapNotNull(byId::get)
|
||||||
|
.flatMapTo(mutableSetOf()) { d -> closure(d, dependentsOf) }
|
||||||
|
if (spec in disablesClosure) return PatchLock.LockedOff(other)
|
||||||
|
}
|
||||||
|
return PatchLock.Free
|
||||||
}
|
}
|
||||||
|
|
||||||
val enabledPatchCount: Int
|
val enabledPatchCount: Int
|
||||||
get() = KnownPatch.All.count { isPatchEnabled(it) }
|
get() = specs.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,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -102,6 +230,79 @@ class PatchOptionsModel(
|
|||||||
componentType = PatchComponent.Type.Patches,
|
componentType = PatchComponent.Type.Patches,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
reloadSpecs(customPatches)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rebuilds [specs] from the selected patch set
|
||||||
|
*/
|
||||||
|
private fun reloadSpecs(component: PatchComponent?) = screenModelScope.launchIO {
|
||||||
|
mainThread { specsLoading = true }
|
||||||
|
val loaded = if (component == null) builtinSpecs else loadManifestSpecs(component) ?: builtinSpecs
|
||||||
|
mainThread {
|
||||||
|
specs = loaded
|
||||||
|
validatePatchSelection()
|
||||||
|
specsLoading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadManifestSpecs(component: PatchComponent): List<PatchSpec>? =
|
||||||
|
loadManifestSpecs(component.getFile(paths))
|
||||||
|
|
||||||
|
private fun loadManifestSpecs(file: File): List<PatchSpec>? {
|
||||||
|
if (!file.exists()) return null
|
||||||
|
return try {
|
||||||
|
val bytes = ZipReader(file).use { it.openEntry(MANIFEST_NAME)?.read() } ?: return null
|
||||||
|
json.decodeFromString(PatchManifest.serializer(), bytes.decodeToString())
|
||||||
|
.patches
|
||||||
|
.takeIf { it.isNotEmpty() }
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
Log.w(BuildConfig.TAG, "Failed to parse $MANIFEST_NAME; using built-in list", t)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pulls the patch list from the latest GitHub release's patches.zip manifest
|
||||||
|
*/
|
||||||
|
private suspend fun loadLatestReleaseSpecs(): List<PatchSpec>? = try {
|
||||||
|
val release = github.getLatestRelease().getOrThrow()
|
||||||
|
val url = release.assets
|
||||||
|
.find { it.name == RadiantLyricsGithubService.PATCHES_ASSET_NAME }
|
||||||
|
?.browserDownloadUrl
|
||||||
|
if (url == null) {
|
||||||
|
Log.w(BuildConfig.TAG, "Latest release ${release.tagName} has no patches.zip asset; using built-in list")
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
// Cached per release tag so re-opening the screen doesn't re-download the same zip.
|
||||||
|
val dest = paths.cacheDownloadDir.resolve("manifest-${release.tagName}.zip")
|
||||||
|
.apply { parentFile?.mkdirs() }
|
||||||
|
if (dest.exists() || downloader.download(url, dest) is IDownloadManager.Result.Success) {
|
||||||
|
loadManifestSpecs(dest).also { loaded ->
|
||||||
|
if (loaded != null) {
|
||||||
|
Log.i(BuildConfig.TAG, "Loaded ${loaded.size} patches from latest release ${release.tagName} manifest")
|
||||||
|
} else {
|
||||||
|
Log.w(BuildConfig.TAG, "Latest release ${release.tagName} ships no manifest.json; using built-in list")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
Log.w(BuildConfig.TAG, "Failed to load latest release manifest; using built-in list", t)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Default (non-custom) source: the latest release manifest, falling back to the built-in list. */
|
||||||
|
private fun loadDefaultSpecs() = screenModelScope.launchIO {
|
||||||
|
mainThread { specsLoading = true }
|
||||||
|
val loaded = loadLatestReleaseSpecs() ?: builtinSpecs
|
||||||
|
mainThread {
|
||||||
|
specs = loaded
|
||||||
|
validatePatchSelection()
|
||||||
|
specsLoading = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val isConfigValid by derivedStateOf {
|
val isConfigValid by derivedStateOf {
|
||||||
@@ -120,9 +321,13 @@ class PatchOptionsModel(
|
|||||||
appName = appName,
|
appName = appName,
|
||||||
packageName = packageName,
|
packageName = packageName,
|
||||||
debuggable = debuggable,
|
debuggable = debuggable,
|
||||||
customInjector = customInjector,
|
customTidalApk = customTidalApk,
|
||||||
customPatches = customPatches,
|
customPatches = customPatches,
|
||||||
disabledPatches = disabledPatches,
|
patchStates = patchStates,
|
||||||
|
selectedVariants = selectedVariants,
|
||||||
|
optionFloats = optionFloats,
|
||||||
|
optionBools = optionBools,
|
||||||
|
optionInts = optionInts,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,11 +352,24 @@ class PatchOptionsModel(
|
|||||||
mainThread { packageNameState = state }
|
mainThread { packageNameState = state }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun validatePatchSelection() {
|
||||||
|
for (spec in specs) {
|
||||||
|
if (isPatchEnabled(spec)) {
|
||||||
|
setPatchEnabled(spec, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
validatePatchSelection()
|
||||||
screenModelScope.launchBlock { fetchPkgNameState() }
|
screenModelScope.launchBlock { fetchPkgNameState() }
|
||||||
|
// Default source is the latest release's manifest; custom selections drive themselves.
|
||||||
|
if (customPatches == null) loadDefaultSpecs()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private const val MANIFEST_NAME = "manifest.json"
|
||||||
|
|
||||||
private val PACKAGE_REGEX = """^[a-z]\w*(\.[a-z]\w*)+$"""
|
private val PACKAGE_REGEX = """^[a-z]\w*(\.[a-z]\w*)+$"""
|
||||||
.toRegex(RegexOption.IGNORE_CASE)
|
.toRegex(RegexOption.IGNORE_CASE)
|
||||||
}
|
}
|
||||||
@@ -162,3 +380,9 @@ enum class PackageNameState {
|
|||||||
Invalid,
|
Invalid,
|
||||||
Taken,
|
Taken,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed class PatchLock {
|
||||||
|
data object Free : PatchLock()
|
||||||
|
data class LockedOn(val by: PatchSpec) : PatchLock()
|
||||||
|
data class LockedOff(val by: PatchSpec) : PatchLock()
|
||||||
|
}
|
||||||
|
|||||||
+28
-13
@@ -55,14 +55,19 @@ 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) },
|
||||||
|
|
||||||
|
specs = model.specs,
|
||||||
enabledPatchCount = model.enabledPatchCount,
|
enabledPatchCount = model.enabledPatchCount,
|
||||||
isPatchEnabled = model::isPatchEnabled,
|
isPatchEnabled = model::isPatchEnabled,
|
||||||
onTogglePatch = model::setPatchEnabled,
|
onTogglePatch = model::setPatchEnabled,
|
||||||
|
patchLockState = model::lockState,
|
||||||
|
variantIndex = model::variantIndex,
|
||||||
|
onSelectVariant = model::selectVariant,
|
||||||
|
optionState = model.optionState,
|
||||||
|
|
||||||
isConfigValid = model.isConfigValid,
|
isConfigValid = model.isConfigValid,
|
||||||
onInstall = {
|
onInstall = {
|
||||||
@@ -88,14 +93,19 @@ 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,
|
||||||
|
|
||||||
|
specs: List<PatchSpec>,
|
||||||
enabledPatchCount: Int,
|
enabledPatchCount: Int,
|
||||||
isPatchEnabled: (KnownPatch) -> Boolean,
|
isPatchEnabled: (PatchSpec) -> Boolean,
|
||||||
onTogglePatch: (KnownPatch, Boolean) -> Unit,
|
onTogglePatch: (PatchSpec, Boolean) -> Unit,
|
||||||
|
patchLockState: (PatchSpec) -> PatchLock,
|
||||||
|
variantIndex: (PatchSpec) -> Int,
|
||||||
|
onSelectVariant: (PatchSpec, Int) -> Unit,
|
||||||
|
optionState: PatchOptionState,
|
||||||
|
|
||||||
isConfigValid: Boolean,
|
isConfigValid: Boolean,
|
||||||
onInstall: () -> Unit,
|
onInstall: () -> Unit,
|
||||||
@@ -158,10 +168,15 @@ fun PatchOptionsScreenContent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
PatchSelectionAccordion(
|
PatchSelectionAccordion(
|
||||||
|
specs = specs,
|
||||||
enabledCount = enabledPatchCount,
|
enabledCount = enabledPatchCount,
|
||||||
totalCount = KnownPatch.All.size,
|
totalCount = specs.size,
|
||||||
isEnabled = isPatchEnabled,
|
isEnabled = isPatchEnabled,
|
||||||
onToggle = onTogglePatch,
|
onToggle = onTogglePatch,
|
||||||
|
lockState = patchLockState,
|
||||||
|
variantIndex = variantIndex,
|
||||||
|
onSelectVariant = onSelectVariant,
|
||||||
|
optionState = optionState,
|
||||||
modifier = Modifier.padding(top = 4.dp),
|
modifier = Modifier.padding(top = 4.dp),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -180,14 +195,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)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,277 @@
|
|||||||
|
package com.meowarex.rlmobile.ui.screens.patchopts
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PatchManifest(
|
||||||
|
val version: Int = 1,
|
||||||
|
val patches: List<PatchSpec> = emptyList(),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@Serializable
|
||||||
|
data class PatchSpec(
|
||||||
|
val id: String,
|
||||||
|
val order: Int = 0,
|
||||||
|
val fileNames: List<String> = emptyList(),
|
||||||
|
val extensionFiles: List<String> = emptyList(),
|
||||||
|
val title: String,
|
||||||
|
val description: String = "",
|
||||||
|
@SerialName("default") val defaultEnabled: Boolean = false,
|
||||||
|
/** ids of patches that get force-enabled when this one is enabled. */
|
||||||
|
val requires: List<String> = emptyList(),
|
||||||
|
/** ids of patches that get force-disabled when this one is enabled. */
|
||||||
|
val disables: List<String> = emptyList(),
|
||||||
|
val variants: List<VariantSpec> = emptyList(),
|
||||||
|
val defaultVariantIndex: Int = 0,
|
||||||
|
val advancedOptions: List<OptionSpec> = emptyList(),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@Serializable
|
||||||
|
data class VariantSpec(
|
||||||
|
val title: String,
|
||||||
|
val fileNames: List<String> = emptyList(),
|
||||||
|
val extensionFiles: List<String> = emptyList(),
|
||||||
|
)
|
||||||
|
|
||||||
|
/** A single advanced option. Discriminated by `"type"` in JSON. */
|
||||||
|
@Serializable
|
||||||
|
sealed interface OptionSpec {
|
||||||
|
val key: String
|
||||||
|
val title: String
|
||||||
|
val description: String
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@Serializable
|
||||||
|
@SerialName("toggle")
|
||||||
|
data class Toggle(
|
||||||
|
override val key: String,
|
||||||
|
override val title: String,
|
||||||
|
override val description: String = "",
|
||||||
|
val default: Boolean = false,
|
||||||
|
/** Patch files applied only while this toggle is on (and its patch is enabled). */
|
||||||
|
val fileNames: List<String> = emptyList(),
|
||||||
|
/** Helper smali extracted only while this toggle is on. */
|
||||||
|
val extensionFiles: List<String> = emptyList(),
|
||||||
|
/** Render inline beneath the variant selector instead of inside the advanced sheet. */
|
||||||
|
val inline: Boolean = false,
|
||||||
|
/** Greyed out (with the lock dialog) unless this variant index is the selected one. */
|
||||||
|
val requiresVariant: Int? = null,
|
||||||
|
/** Greyed out unless the sibling option with this key is currently on. */
|
||||||
|
val requiresOption: String? = null,
|
||||||
|
/** Variant indices hidden from the picker while this toggle is on. */
|
||||||
|
val hidesVariants: List<Int> = emptyList(),
|
||||||
|
/** Variant title overrides (index -> title) applied while this toggle is on. */
|
||||||
|
val relabelVariants: Map<Int, String> = emptyMap(),
|
||||||
|
/** Placeholder name (without the surrounding `__`) baked into the `.patch` files. */
|
||||||
|
val token: String? = null,
|
||||||
|
) : OptionSpec
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@Serializable
|
||||||
|
@SerialName("slider")
|
||||||
|
data class Slider(
|
||||||
|
override val key: String,
|
||||||
|
override val title: String,
|
||||||
|
override val description: String = "",
|
||||||
|
val default: Float = 0f,
|
||||||
|
val min: Float = 0f,
|
||||||
|
val max: Float = 100f,
|
||||||
|
val steps: Int = 0,
|
||||||
|
val displayAsPercent: Boolean = false,
|
||||||
|
val unit: String? = null,
|
||||||
|
/** Placeholder name (without the surrounding `__`) baked into the `.patch` files. */
|
||||||
|
val token: String? = null,
|
||||||
|
/** How the chosen value becomes the smali literal that replaces the token. */
|
||||||
|
val encode: SmaliEncode? = null,
|
||||||
|
) : OptionSpec {
|
||||||
|
val valueRange: ClosedFloatingPointRange<Float> get() = min..max
|
||||||
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@Serializable
|
||||||
|
@SerialName("choice")
|
||||||
|
data class Choice(
|
||||||
|
override val key: String,
|
||||||
|
override val title: String = "",
|
||||||
|
override val description: String = "",
|
||||||
|
val entries: List<String> = emptyList(),
|
||||||
|
val defaultIndex: Int = 0,
|
||||||
|
val values: List<String> = emptyList(),
|
||||||
|
val requiresOption: String? = null,
|
||||||
|
val token: String? = null,
|
||||||
|
) : OptionSpec
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
enum class EncodeKind {
|
||||||
|
@SerialName("floatBits")
|
||||||
|
FloatBits,
|
||||||
|
|
||||||
|
@SerialName("argbAlpha")
|
||||||
|
ArgbAlpha,
|
||||||
|
|
||||||
|
@SerialName("int")
|
||||||
|
IntValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@Serializable
|
||||||
|
data class SmaliEncode(
|
||||||
|
val kind: EncodeKind,
|
||||||
|
val scale: Float = 1f,
|
||||||
|
) {
|
||||||
|
fun encode(value: Float): String = when (kind) {
|
||||||
|
// Float dp bit-pattern: const vX, <bits>; Dp.constructor-impl(F)F
|
||||||
|
EncodeKind.FloatBits -> (value * scale).toRawBits().toString()
|
||||||
|
// ARGB int with black RGB and a percentage-derived alpha: const vX, <argb>
|
||||||
|
EncodeKind.ArgbAlpha -> ((value / 100f * 255f).roundToInt() shl 24).toString()
|
||||||
|
// Plain (optionally scaled) integer literal.
|
||||||
|
EncodeKind.IntValue -> (value * scale).roundToInt().toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun builtinPatchSpecs(resolve: (Int) -> String): List<PatchSpec> =
|
||||||
|
KnownPatch.All.map { patch ->
|
||||||
|
PatchSpec(
|
||||||
|
id = patch.name,
|
||||||
|
order = patch.order,
|
||||||
|
fileNames = patch.fileNames,
|
||||||
|
extensionFiles = patch.extensionFiles,
|
||||||
|
title = resolve(patch.titleRes),
|
||||||
|
description = resolve(patch.descRes),
|
||||||
|
defaultEnabled = patch.default.isEnabled,
|
||||||
|
requires = patch.requires.map { it.name },
|
||||||
|
disables = patch.disables.map { it.name },
|
||||||
|
variants = patch.variants.map { VariantSpec(resolve(it.titleRes), it.fileNames, it.extensionFiles) },
|
||||||
|
defaultVariantIndex = patch.defaultVariantIndex,
|
||||||
|
advancedOptions = patch.advancedOptions.map { it.toSpec(resolve) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PatchOption.toSpec(resolve: (Int) -> String): OptionSpec = when (this) {
|
||||||
|
is PatchOption.Toggle -> OptionSpec.Toggle(
|
||||||
|
key = key,
|
||||||
|
title = resolve(titleRes),
|
||||||
|
description = resolve(descRes),
|
||||||
|
default = default,
|
||||||
|
fileNames = fileNames,
|
||||||
|
extensionFiles = extensionFiles,
|
||||||
|
inline = inline,
|
||||||
|
requiresVariant = requiresVariant,
|
||||||
|
requiresOption = requiresOption,
|
||||||
|
hidesVariants = hidesVariants,
|
||||||
|
relabelVariants = relabelVariants.mapValues { resolve(it.value) },
|
||||||
|
token = token,
|
||||||
|
)
|
||||||
|
|
||||||
|
is PatchOption.Slider -> OptionSpec.Slider(
|
||||||
|
key = key,
|
||||||
|
title = resolve(titleRes),
|
||||||
|
description = resolve(descRes),
|
||||||
|
default = default,
|
||||||
|
min = valueRange.start,
|
||||||
|
max = valueRange.endInclusive,
|
||||||
|
steps = steps,
|
||||||
|
displayAsPercent = displayAsPercent,
|
||||||
|
unit = unitRes?.let(resolve),
|
||||||
|
token = token,
|
||||||
|
encode = encode,
|
||||||
|
)
|
||||||
|
|
||||||
|
is PatchOption.Choice -> OptionSpec.Choice(
|
||||||
|
key = key,
|
||||||
|
title = resolve(titleRes),
|
||||||
|
description = resolve(descRes),
|
||||||
|
entries = entries.map { resolve(it.labelRes) },
|
||||||
|
defaultIndex = defaultIndex,
|
||||||
|
values = entries.map { it.value ?: "" },
|
||||||
|
requiresOption = requiresOption,
|
||||||
|
token = token,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class EffectiveVariant(val originalIndex: Int, val title: String)
|
||||||
|
|
||||||
|
fun PatchSpec.effectiveVariants(isOptionOn: (OptionSpec.Toggle) -> Boolean): List<EffectiveVariant> {
|
||||||
|
if (variants.isEmpty()) return emptyList()
|
||||||
|
val hidden = mutableSetOf<Int>()
|
||||||
|
val relabel = mutableMapOf<Int, String>()
|
||||||
|
for (option in advancedOptions) {
|
||||||
|
if (option is OptionSpec.Toggle && isOptionOn(option)) {
|
||||||
|
hidden += option.hidesVariants
|
||||||
|
relabel += option.relabelVariants
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return variants.mapIndexedNotNull { index, variant ->
|
||||||
|
if (index in hidden) null else EffectiveVariant(index, relabel[index] ?: variant.title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun PatchSpec.resolveVariantIndex(stored: Int, isOptionOn: (OptionSpec.Toggle) -> Boolean): Int {
|
||||||
|
val visible = effectiveVariants(isOptionOn)
|
||||||
|
if (visible.isEmpty() || visible.any { it.originalIndex == stored }) return stored
|
||||||
|
val relabelKeys = buildSet {
|
||||||
|
for (option in advancedOptions) {
|
||||||
|
if (option is OptionSpec.Toggle && isOptionOn(option)) addAll(option.relabelVariants.keys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return visible.firstOrNull { it.originalIndex in relabelKeys }?.originalIndex
|
||||||
|
?: visible.first().originalIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface OptionLock {
|
||||||
|
data object Free : OptionLock
|
||||||
|
data class NeedsVariant(val title: String) : OptionLock
|
||||||
|
data class NeedsOption(val title: String) : OptionLock
|
||||||
|
}
|
||||||
|
|
||||||
|
fun PatchSpec.optionLock(
|
||||||
|
option: OptionSpec,
|
||||||
|
selectedVariant: Int,
|
||||||
|
isOptionOn: (OptionSpec.Toggle) -> Boolean,
|
||||||
|
): OptionLock {
|
||||||
|
if (option !is OptionSpec.Toggle) return OptionLock.Free
|
||||||
|
option.requiresVariant?.let { required ->
|
||||||
|
if (selectedVariant != required) {
|
||||||
|
variants.getOrNull(required)?.let { return OptionLock.NeedsVariant(it.title) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
option.requiresOption?.let { key ->
|
||||||
|
val required = advancedOptions.filterIsInstance<OptionSpec.Toggle>().firstOrNull { it.key == key }
|
||||||
|
if (required != null && !isOptionOn(required)) return OptionLock.NeedsOption(required.title)
|
||||||
|
}
|
||||||
|
return OptionLock.Free
|
||||||
|
}
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
class PatchOptionState(
|
||||||
|
val toggle: (PatchSpec, OptionSpec.Toggle) -> Boolean,
|
||||||
|
val setToggle: (PatchSpec, OptionSpec.Toggle, Boolean) -> Unit,
|
||||||
|
val slider: (PatchSpec, OptionSpec.Slider) -> Float,
|
||||||
|
val setSlider: (PatchSpec, OptionSpec.Slider, Float) -> Unit,
|
||||||
|
val choice: (PatchSpec, OptionSpec.Choice) -> Int,
|
||||||
|
val setChoice: (PatchSpec, OptionSpec.Choice, Int) -> Unit,
|
||||||
|
val isModified: (PatchSpec) -> Boolean,
|
||||||
|
val reset: (PatchSpec) -> Unit,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
/** Read-only stub returning defaults — for Compose previews. */
|
||||||
|
val Preview = PatchOptionState(
|
||||||
|
toggle = { _, option -> option.default },
|
||||||
|
setToggle = { _, _, _ -> },
|
||||||
|
slider = { _, option -> option.default },
|
||||||
|
setSlider = { _, _, _ -> },
|
||||||
|
choice = { _, option -> option.defaultIndex },
|
||||||
|
setChoice = { _, _, _ -> },
|
||||||
|
isModified = { false },
|
||||||
|
reset = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+473
@@ -0,0 +1,473 @@
|
|||||||
|
package com.meowarex.rlmobile.ui.screens.patchopts.components
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.semantics.Role
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
import androidx.compose.ui.text.fromHtml
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.meowarex.rlmobile.R
|
||||||
|
import com.meowarex.rlmobile.ui.components.ResetToDefaultButton
|
||||||
|
import com.meowarex.rlmobile.ui.screens.patchopts.OptionLock
|
||||||
|
import com.meowarex.rlmobile.ui.screens.patchopts.OptionSpec
|
||||||
|
import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptionState
|
||||||
|
import com.meowarex.rlmobile.ui.screens.patchopts.optionLock
|
||||||
|
import com.meowarex.rlmobile.ui.screens.patchopts.PatchSpec
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun PatchAdvancedOptionsSheet(
|
||||||
|
patch: PatchSpec,
|
||||||
|
state: PatchOptionState,
|
||||||
|
selectedVariant: Int,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
) {
|
||||||
|
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
var lockDialog by remember { mutableStateOf<OptionLock?>(null) }
|
||||||
|
|
||||||
|
fun dismiss() {
|
||||||
|
scope.launch { sheetState.hide() }.invokeOnCompletion {
|
||||||
|
if (!sheetState.isVisible) onDismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ModalBottomSheet(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
sheetState = sheetState,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(horizontal = 24.dp)
|
||||||
|
.padding(bottom = 16.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(20.dp),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.patchopts_advanced_sheet_label),
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = patch.title,
|
||||||
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ResetToDefaultButton(
|
||||||
|
enabled = state.isModified(patch),
|
||||||
|
onClick = { state.reset(patch) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
HorizontalDivider()
|
||||||
|
|
||||||
|
val parentKeys = patch.advancedOptions
|
||||||
|
.filterIsInstance<OptionSpec.Toggle>()
|
||||||
|
.filter { !it.inline }
|
||||||
|
.map { it.key }
|
||||||
|
.toSet()
|
||||||
|
val sheetOptions = patch.advancedOptions.filter { option ->
|
||||||
|
when {
|
||||||
|
option is OptionSpec.Toggle && option.inline -> false
|
||||||
|
option is OptionSpec.Toggle && option.requiresOption in parentKeys -> false
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (option in sheetOptions) key(option.key) {
|
||||||
|
val lock = patch.optionLock(option, selectedVariant) { state.toggle(patch, it) }
|
||||||
|
when (option) {
|
||||||
|
is OptionSpec.Toggle -> {
|
||||||
|
val toggleOn = state.toggle(patch, option)
|
||||||
|
val gatedChoices = patch.advancedOptions.filterIsInstance<OptionSpec.Choice>()
|
||||||
|
.filter { it.requiresOption == option.key }
|
||||||
|
val gatedToggles = patch.advancedOptions.filterIsInstance<OptionSpec.Toggle>()
|
||||||
|
.filter { it.requiresOption == option.key && !it.inline }
|
||||||
|
val hasSubOptions = gatedChoices.isNotEmpty() || gatedToggles.isNotEmpty()
|
||||||
|
val carded = hasSubOptions && toggleOn
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = if (carded) {
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(MaterialTheme.shapes.medium)
|
||||||
|
.background(MaterialTheme.colorScheme.primary.copy(alpha = 0.07f))
|
||||||
|
.border(
|
||||||
|
width = 1.5.dp,
|
||||||
|
color = MaterialTheme.colorScheme.primary.copy(alpha = 0.55f),
|
||||||
|
shape = MaterialTheme.shapes.medium,
|
||||||
|
)
|
||||||
|
.padding(horizontal = 12.dp, vertical = 4.dp)
|
||||||
|
} else {
|
||||||
|
Modifier.fillMaxWidth()
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
ToggleOptionRow(
|
||||||
|
title = option.title,
|
||||||
|
description = option.description,
|
||||||
|
checked = toggleOn,
|
||||||
|
onCheckedChange = { state.setToggle(patch, option, it) },
|
||||||
|
locked = lock != OptionLock.Free,
|
||||||
|
onLockedTap = { lockDialog = lock },
|
||||||
|
modifier = if (hasSubOptions) Modifier.padding(vertical = 6.dp) else Modifier,
|
||||||
|
)
|
||||||
|
if (hasSubOptions) {
|
||||||
|
AnimatedVisibility(visible = toggleOn) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 8.dp),
|
||||||
|
) {
|
||||||
|
gatedChoices.forEach { choice ->
|
||||||
|
key(choice.key) {
|
||||||
|
ChoiceOptionRow(
|
||||||
|
title = choice.title,
|
||||||
|
entries = choice.entries,
|
||||||
|
selectedIndex = state.choice(patch, choice),
|
||||||
|
onSelect = { state.setChoice(patch, choice, it) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gatedToggles.forEach { sub ->
|
||||||
|
key(sub.key) {
|
||||||
|
val subLock = patch.optionLock(sub, selectedVariant) { state.toggle(patch, it) }
|
||||||
|
ToggleOptionRow(
|
||||||
|
title = sub.title,
|
||||||
|
description = sub.description,
|
||||||
|
checked = state.toggle(patch, sub),
|
||||||
|
onCheckedChange = { state.setToggle(patch, sub, it) },
|
||||||
|
locked = subLock != OptionLock.Free,
|
||||||
|
onLockedTap = { lockDialog = subLock },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is OptionSpec.Slider -> SliderOptionRow(
|
||||||
|
title = option.title,
|
||||||
|
description = option.description,
|
||||||
|
value = state.slider(patch, option),
|
||||||
|
valueLabel = formatSliderValue(option, state.slider(patch, option)),
|
||||||
|
valueRange = option.valueRange,
|
||||||
|
steps = option.steps,
|
||||||
|
onValueChange = { state.setSlider(patch, option, it) },
|
||||||
|
)
|
||||||
|
|
||||||
|
is OptionSpec.Choice ->
|
||||||
|
if (option.requiresOption == null) {
|
||||||
|
ChoiceOptionRow(
|
||||||
|
title = option.title,
|
||||||
|
entries = option.entries,
|
||||||
|
selectedIndex = state.choice(patch, option),
|
||||||
|
onSelect = { state.setChoice(patch, option, it) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FilledTonalButton(
|
||||||
|
onClick = { dismiss() },
|
||||||
|
colors = ButtonDefaults.filledTonalButtonColors(
|
||||||
|
contentColor = MaterialTheme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.End)
|
||||||
|
.padding(top = 4.dp),
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.action_done))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lockDialog?.let { lock ->
|
||||||
|
OptionLockDialog(lock = lock, onDismiss = { lockDialog = null })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ToggleOptionRow(
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
checked: Boolean,
|
||||||
|
onCheckedChange: (Boolean) -> Unit,
|
||||||
|
locked: Boolean = false,
|
||||||
|
onLockedTap: () -> Unit = {},
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val interactionSource = remember(::MutableInteractionSource)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable(
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
indication = null,
|
||||||
|
role = Role.Switch,
|
||||||
|
) { if (locked) onLockedTap() else onCheckedChange(!checked) }
|
||||||
|
.alpha(if (locked) 0.45f else 1f),
|
||||||
|
) {
|
||||||
|
OptionText(
|
||||||
|
title = title,
|
||||||
|
description = description,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
)
|
||||||
|
|
||||||
|
Box {
|
||||||
|
Switch(
|
||||||
|
checked = checked,
|
||||||
|
enabled = !locked,
|
||||||
|
onCheckedChange = onCheckedChange,
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
)
|
||||||
|
if (locked) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.matchParentSize()
|
||||||
|
.clickable(
|
||||||
|
interactionSource = remember(::MutableInteractionSource),
|
||||||
|
indication = null,
|
||||||
|
role = Role.Switch,
|
||||||
|
) { onLockedTap() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SliderOptionRow(
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
value: Float,
|
||||||
|
valueLabel: String,
|
||||||
|
valueRange: ClosedFloatingPointRange<Float>,
|
||||||
|
steps: Int,
|
||||||
|
onValueChange: (Float) -> Unit,
|
||||||
|
) {
|
||||||
|
// Whether the slider snaps to the step dots
|
||||||
|
var snap by rememberSaveable { mutableStateOf(true) }
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
|
) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = valueLabel,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
)
|
||||||
|
if (steps > 0) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
snap = !snap
|
||||||
|
if (snap) onValueChange(snapToNearestStep(value, valueRange, steps))
|
||||||
|
},
|
||||||
|
modifier = Modifier.size(32.dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(
|
||||||
|
if (snap) R.drawable.ic_lock else R.drawable.ic_lock_open,
|
||||||
|
),
|
||||||
|
contentDescription = stringResource(R.string.patchopts_toggle_snap),
|
||||||
|
tint = if (snap) MaterialTheme.colorScheme.primary
|
||||||
|
else MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.size(18.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = description,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
modifier = Modifier.alpha(.7f),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Locked -> native stepped slider (dots + snapping), left exactly as-is
|
||||||
|
// Unlocked -> plain continuous slider with the trailing stop-indicator dot removed
|
||||||
|
if (snap) {
|
||||||
|
Slider(
|
||||||
|
value = value,
|
||||||
|
onValueChange = onValueChange,
|
||||||
|
valueRange = valueRange,
|
||||||
|
steps = steps,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Slider(
|
||||||
|
value = value,
|
||||||
|
onValueChange = onValueChange,
|
||||||
|
valueRange = valueRange,
|
||||||
|
track = { SliderDefaults.Track(sliderState = it, drawStopIndicator = null) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun snapToNearestStep(
|
||||||
|
value: Float,
|
||||||
|
range: ClosedFloatingPointRange<Float>,
|
||||||
|
steps: Int,
|
||||||
|
): Float {
|
||||||
|
if (steps <= 0) return value
|
||||||
|
val stepSize = (range.endInclusive - range.start) / (steps + 1)
|
||||||
|
if (stepSize <= 0f) return value
|
||||||
|
val snapped = range.start + Math.round((value - range.start) / stepSize) * stepSize
|
||||||
|
return snapped.coerceIn(range.start, range.endInclusive)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ChoiceOptionRow(
|
||||||
|
entries: List<String>,
|
||||||
|
selectedIndex: Int,
|
||||||
|
onSelect: (Int) -> Unit,
|
||||||
|
title: String = "",
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier.fillMaxWidth(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
if (title.isNotEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
entries.forEachIndexed { index, entry ->
|
||||||
|
SegmentedButton(
|
||||||
|
selected = index == selectedIndex,
|
||||||
|
onClick = { onSelect(index) },
|
||||||
|
shape = SegmentedButtonDefaults.itemShape(index = index, count = entries.size),
|
||||||
|
icon = {},
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
text = entry,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun OptionText(
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = description,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
modifier = Modifier.alpha(.7f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatSliderValue(option: OptionSpec.Slider, value: Float): String {
|
||||||
|
val rounded = value.roundToInt()
|
||||||
|
val unit = option.unit
|
||||||
|
return when {
|
||||||
|
option.displayAsPercent -> "$rounded%"
|
||||||
|
unit != null -> "$rounded $unit"
|
||||||
|
else -> rounded.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun OptionLockDialog(lock: OptionLock, onDismiss: () -> Unit) {
|
||||||
|
val message = when (lock) {
|
||||||
|
is OptionLock.NeedsVariant -> stringResource(R.string.patch_opt_lock_needs_variant, lock.title)
|
||||||
|
is OptionLock.NeedsOption -> stringResource(R.string.patch_opt_lock_needs_option, lock.title)
|
||||||
|
OptionLock.Free -> return
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicAlertDialog(onDismissRequest = onDismiss) {
|
||||||
|
Surface(
|
||||||
|
shape = MaterialTheme.shapes.large,
|
||||||
|
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||||
|
tonalElevation = 6.dp,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(start = 24.dp, end = 12.dp, top = 20.dp, bottom = 8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.patch_opt_lock_title),
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = AnnotatedString.fromHtml(message),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
)
|
||||||
|
Box(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
TextButton(
|
||||||
|
onClick = onDismiss,
|
||||||
|
modifier = Modifier.align(Alignment.CenterEnd),
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.action_got_it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+270
-14
@@ -4,9 +4,11 @@ package com.meowarex.rlmobile.ui.screens.patchopts.components
|
|||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
@@ -18,16 +20,30 @@ import androidx.compose.ui.draw.rotate
|
|||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
import androidx.compose.ui.text.fromHtml
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.meowarex.rlmobile.R
|
import com.meowarex.rlmobile.R
|
||||||
import com.meowarex.rlmobile.ui.screens.patchopts.KnownPatch
|
import com.meowarex.rlmobile.ui.screens.patchopts.OptionSpec
|
||||||
|
import com.meowarex.rlmobile.ui.screens.patchopts.PatchLock
|
||||||
|
import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptionState
|
||||||
|
import com.meowarex.rlmobile.ui.screens.patchopts.PatchSpec
|
||||||
|
import com.meowarex.rlmobile.ui.screens.patchopts.effectiveVariants
|
||||||
|
import com.meowarex.rlmobile.ui.screens.patchopts.resolveVariantIndex
|
||||||
|
|
||||||
|
private data class LockInfo(val patch: PatchSpec, val lock: PatchLock)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PatchSelectionAccordion(
|
fun PatchSelectionAccordion(
|
||||||
|
specs: List<PatchSpec>,
|
||||||
enabledCount: Int,
|
enabledCount: Int,
|
||||||
totalCount: Int,
|
totalCount: Int,
|
||||||
isEnabled: (KnownPatch) -> Boolean,
|
isEnabled: (PatchSpec) -> Boolean,
|
||||||
onToggle: (KnownPatch, Boolean) -> Unit,
|
onToggle: (PatchSpec, Boolean) -> Unit,
|
||||||
|
lockState: (PatchSpec) -> PatchLock,
|
||||||
|
variantIndex: (PatchSpec) -> Int,
|
||||||
|
onSelectVariant: (PatchSpec, Int) -> Unit,
|
||||||
|
optionState: PatchOptionState,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
var expanded by rememberSaveable { mutableStateOf(false) }
|
var expanded by rememberSaveable { mutableStateOf(false) }
|
||||||
@@ -36,6 +52,9 @@ fun PatchSelectionAccordion(
|
|||||||
label = "patch-accordion-arrow",
|
label = "patch-accordion-arrow",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var lockInfo by remember { mutableStateOf<LockInfo?>(null) }
|
||||||
|
var advancedFor by remember { mutableStateOf<PatchSpec?>(null) }
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -80,6 +99,7 @@ fun PatchSelectionAccordion(
|
|||||||
|
|
||||||
AnimatedVisibility(visible = expanded) {
|
AnimatedVisibility(visible = expanded) {
|
||||||
Column(
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(MaterialTheme.colorScheme.background.copy(0.4f))
|
.background(MaterialTheme.colorScheme.background.copy(0.4f))
|
||||||
@@ -93,21 +113,145 @@ fun PatchSelectionAccordion(
|
|||||||
.padding(bottom = 8.dp),
|
.padding(bottom = 8.dp),
|
||||||
)
|
)
|
||||||
|
|
||||||
for (patch in KnownPatch.All) key(patch) {
|
for (patch in specs) key(patch.id) {
|
||||||
PatchCheckboxRow(
|
val checked = isEnabled(patch)
|
||||||
title = stringResource(patch.titleRes),
|
val lock = lockState(patch)
|
||||||
description = stringResource(patch.descRes),
|
|
||||||
checked = isEnabled(patch),
|
val inlineToggles = patch.advancedOptions
|
||||||
onCheckedChange = { onToggle(patch, it) },
|
.filterIsInstance<OptionSpec.Toggle>()
|
||||||
)
|
.filter { it.inline }
|
||||||
|
val hasSheetOptions = patch.advancedOptions.any { it !is OptionSpec.Toggle || !it.inline }
|
||||||
|
val hasSettings = patch.variants.isNotEmpty() || inlineToggles.isNotEmpty() || hasSheetOptions
|
||||||
|
|
||||||
|
// When a patch is enabled and has settings show a carded view of the settings
|
||||||
|
val carded = checked && hasSettings
|
||||||
|
Column(
|
||||||
|
modifier = if (carded) {
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(MaterialTheme.shapes.medium)
|
||||||
|
.background(MaterialTheme.colorScheme.primary.copy(alpha = 0.07f))
|
||||||
|
.border(
|
||||||
|
width = 1.5.dp,
|
||||||
|
color = MaterialTheme.colorScheme.primary.copy(alpha = 0.55f),
|
||||||
|
shape = MaterialTheme.shapes.medium,
|
||||||
|
)
|
||||||
|
.padding(horizontal = 12.dp, vertical = 4.dp)
|
||||||
|
} else {
|
||||||
|
Modifier.fillMaxWidth()
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
PatchSwitchRow(
|
||||||
|
title = patch.title,
|
||||||
|
description = patch.description,
|
||||||
|
checked = checked,
|
||||||
|
lock = lock,
|
||||||
|
onCheckedChange = { onToggle(patch, it) },
|
||||||
|
onLockedTap = { lockInfo = LockInfo(patch, lock) },
|
||||||
|
)
|
||||||
|
|
||||||
|
if (hasSettings) {
|
||||||
|
AnimatedVisibility(visible = checked) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 8.dp),
|
||||||
|
) {
|
||||||
|
if (patch.variants.isNotEmpty()) {
|
||||||
|
val effective = patch.effectiveVariants { optionState.toggle(patch, it) }
|
||||||
|
val resolved = patch.resolveVariantIndex(variantIndex(patch)) {
|
||||||
|
optionState.toggle(patch, it)
|
||||||
|
}
|
||||||
|
PatchVariantSelector(
|
||||||
|
variants = effective,
|
||||||
|
selectedIndex = effective
|
||||||
|
.indexOfFirst { it.originalIndex == resolved }
|
||||||
|
.coerceAtLeast(0),
|
||||||
|
onSelect = { pos ->
|
||||||
|
effective.getOrNull(pos)?.let { onSelectVariant(patch, it.originalIndex) }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (option in inlineToggles) key(option.key) {
|
||||||
|
InlineToggleRow(
|
||||||
|
title = option.title,
|
||||||
|
description = option.description,
|
||||||
|
checked = optionState.toggle(patch, option),
|
||||||
|
onCheckedChange = { optionState.setToggle(patch, option, it) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasSheetOptions) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
AdvancedOptionsButton(
|
||||||
|
modified = optionState.isModified(patch),
|
||||||
|
onClick = { advancedFor = patch },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lockInfo?.let { info ->
|
||||||
|
PatchLockDialog(
|
||||||
|
thisPatch = info.patch,
|
||||||
|
lock = info.lock,
|
||||||
|
onDismiss = { lockInfo = null },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
advancedFor?.let { patch ->
|
||||||
|
PatchAdvancedOptionsSheet(
|
||||||
|
patch = patch,
|
||||||
|
state = optionState,
|
||||||
|
selectedVariant = patch.resolveVariantIndex(variantIndex(patch)) { optionState.toggle(patch, it) },
|
||||||
|
onDismiss = { advancedFor = null },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AdvancedOptionsButton(
|
||||||
|
modified: Boolean,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
FilledTonalButton(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = modifier,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_tune),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(18.dp),
|
||||||
|
)
|
||||||
|
Spacer(Modifier.width(8.dp))
|
||||||
|
Text(stringResource(R.string.patchopts_advanced_button))
|
||||||
|
if (modified) {
|
||||||
|
Spacer(Modifier.width(8.dp))
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(8.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(MaterialTheme.colorScheme.primary),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun PatchCheckboxRow(
|
private fun InlineToggleRow(
|
||||||
title: String,
|
title: String,
|
||||||
description: String,
|
description: String,
|
||||||
checked: Boolean,
|
checked: Boolean,
|
||||||
@@ -117,22 +261,62 @@ private fun PatchCheckboxRow(
|
|||||||
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable(
|
.clickable(
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
indication = null,
|
indication = null,
|
||||||
role = Role.Checkbox,
|
role = Role.Switch,
|
||||||
) { onCheckedChange(!checked) }
|
) { onCheckedChange(!checked) }
|
||||||
.padding(vertical = 4.dp),
|
.padding(vertical = 4.dp),
|
||||||
) {
|
) {
|
||||||
Checkbox(
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
) {
|
||||||
|
Text(text = title, style = MaterialTheme.typography.titleSmall)
|
||||||
|
Text(
|
||||||
|
text = description,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
modifier = Modifier.alpha(.7f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Switch(
|
||||||
checked = checked,
|
checked = checked,
|
||||||
onCheckedChange = onCheckedChange,
|
onCheckedChange = onCheckedChange,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun PatchSwitchRow(
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
checked: Boolean,
|
||||||
|
lock: PatchLock,
|
||||||
|
onCheckedChange: (Boolean) -> Unit,
|
||||||
|
onLockedTap: () -> Unit,
|
||||||
|
) {
|
||||||
|
val interactionSource = remember(::MutableInteractionSource)
|
||||||
|
val isLocked = lock !is PatchLock.Free
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable(
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
indication = null,
|
||||||
|
role = Role.Switch,
|
||||||
|
) {
|
||||||
|
if (isLocked) onLockedTap() else onCheckedChange(!checked)
|
||||||
|
}
|
||||||
|
.alpha(if (isLocked) 0.45f else 1f)
|
||||||
|
.padding(vertical = 6.dp),
|
||||||
|
) {
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
@@ -147,5 +331,77 @@ private fun PatchCheckboxRow(
|
|||||||
modifier = Modifier.alpha(.7f),
|
modifier = Modifier.alpha(.7f),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Box {
|
||||||
|
Switch(
|
||||||
|
checked = checked,
|
||||||
|
enabled = !isLocked,
|
||||||
|
onCheckedChange = onCheckedChange,
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
)
|
||||||
|
if (isLocked) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.matchParentSize()
|
||||||
|
.clickable(
|
||||||
|
interactionSource = remember(::MutableInteractionSource),
|
||||||
|
indication = null,
|
||||||
|
role = Role.Switch,
|
||||||
|
) { onLockedTap() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun PatchLockDialog(
|
||||||
|
thisPatch: PatchSpec,
|
||||||
|
lock: PatchLock,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
) {
|
||||||
|
val (titleRes, msgRes, blockerTitle) = when (lock) {
|
||||||
|
is PatchLock.LockedOn -> Triple(
|
||||||
|
R.string.patch_lock_required_title,
|
||||||
|
R.string.patch_lock_required_msg,
|
||||||
|
lock.by.title,
|
||||||
|
)
|
||||||
|
is PatchLock.LockedOff -> Triple(
|
||||||
|
R.string.patch_lock_blocked_title,
|
||||||
|
R.string.patch_lock_blocked_msg,
|
||||||
|
lock.by.title,
|
||||||
|
)
|
||||||
|
PatchLock.Free -> return
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicAlertDialog(onDismissRequest = onDismiss) {
|
||||||
|
Surface(
|
||||||
|
shape = MaterialTheme.shapes.large,
|
||||||
|
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||||
|
tonalElevation = 6.dp,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(start = 24.dp, end = 12.dp, top = 20.dp, bottom = 8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(titleRes),
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = AnnotatedString.fromHtml(stringResource(msgRes, blockerTitle)),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
)
|
||||||
|
Box(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
TextButton(
|
||||||
|
onClick = onDismiss,
|
||||||
|
modifier = Modifier.align(Alignment.CenterEnd),
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.action_got_it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
package com.meowarex.rlmobile.ui.screens.patchopts.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import com.meowarex.rlmobile.ui.screens.patchopts.EffectiveVariant
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun PatchVariantSelector(
|
||||||
|
variants: List<EffectiveVariant>,
|
||||||
|
selectedIndex: Int,
|
||||||
|
onSelect: (Int) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
SingleChoiceSegmentedButtonRow(
|
||||||
|
modifier = modifier.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
variants.forEachIndexed { index, variant ->
|
||||||
|
SegmentedButton(
|
||||||
|
selected = index == selectedIndex,
|
||||||
|
onClick = { onSelect(index) },
|
||||||
|
shape = SegmentedButtonDefaults.itemShape(
|
||||||
|
index = index,
|
||||||
|
count = variants.size,
|
||||||
|
),
|
||||||
|
icon = {},
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
text = variant.title,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+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),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
@@ -55,6 +55,10 @@ class UpdaterViewModel(
|
|||||||
showDialog = false
|
showDialog = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun reopenDialog() {
|
||||||
|
if (targetVersion != null) showDialog = true
|
||||||
|
}
|
||||||
|
|
||||||
fun triggerUpdate() = viewModelScope.launchIO {
|
fun triggerUpdate() = viewModelScope.launchIO {
|
||||||
if (!isWorking.compareAndSet(expect = false, update = true))
|
if (!isWorking.compareAndSet(expect = false, update = true))
|
||||||
return@launchIO
|
return@launchIO
|
||||||
|
|||||||
+9
-5
@@ -7,7 +7,6 @@ import android.app.PendingIntent
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
@@ -51,15 +50,15 @@ class UpdateCheckWorker(
|
|||||||
.maxByOrNull { it.first }
|
.maxByOrNull { it.first }
|
||||||
?: return Result.success()
|
?: return Result.success()
|
||||||
|
|
||||||
val (version, release) = latestVersion
|
val (version, _) = latestVersion
|
||||||
if (current >= version) return Result.success()
|
if (current >= version) return Result.success()
|
||||||
|
|
||||||
Log.i(BuildConfig.TAG, "Update available: $version (installed $current)")
|
Log.i(BuildConfig.TAG, "Update available: $version (installed $current)")
|
||||||
postUpdateNotification(version.toString(), release.htmlUrl)
|
postUpdateNotification(version.toString())
|
||||||
return Result.success()
|
return Result.success()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun postUpdateNotification(version: String, releaseUrl: String) {
|
private fun postUpdateNotification(version: String) {
|
||||||
val nm = applicationContext.getSystemService<NotificationManager>() ?: return
|
val nm = applicationContext.getSystemService<NotificationManager>() ?: return
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
@@ -78,10 +77,15 @@ class UpdateCheckWorker(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val launchIntent = applicationContext.packageManager
|
||||||
|
.getLaunchIntentForPackage(applicationContext.packageName)
|
||||||
|
?.apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) }
|
||||||
|
?: return
|
||||||
|
|
||||||
val pendingIntent = PendingIntent.getActivity(
|
val pendingIntent = PendingIntent.getActivity(
|
||||||
applicationContext,
|
applicationContext,
|
||||||
0,
|
0,
|
||||||
Intent(Intent.ACTION_VIEW, Uri.parse(releaseUrl)).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) },
|
launchIntent,
|
||||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
|
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,9 @@ import android.app.Activity
|
|||||||
import android.content.*
|
import android.content.*
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.net.ConnectivityManager
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.*
|
import android.os.*
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.telephony.TelephonyManager
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
@@ -152,37 +150,6 @@ suspend fun Context.isPlayProtectEnabled(): Boolean? {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether the device is connected on a metered WIFI connection or through any type of mobile data,
|
|
||||||
* to avoid unknowingly downloading a lot of stuff through a potentially metered network.
|
|
||||||
*/
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
fun Context.isNetworkDangerous(): Boolean {
|
|
||||||
val connectivity = this.getSystemService<ConnectivityManager>()
|
|
||||||
?: error("Unable to get system connectivity service")
|
|
||||||
|
|
||||||
if (connectivity.isActiveNetworkMetered) return true
|
|
||||||
|
|
||||||
when (val info = connectivity.activeNetworkInfo) {
|
|
||||||
null -> return false
|
|
||||||
else -> {
|
|
||||||
if (info.isRoaming) return true
|
|
||||||
if (info.type == ConnectivityManager.TYPE_WIFI) return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val telephony = this.getSystemService<TelephonyManager>()
|
|
||||||
?: error("Unable to get system telephony service")
|
|
||||||
|
|
||||||
val dangerousMobileDataStates = arrayOf(
|
|
||||||
/* TelephonyManager.DATA_DISCONNECTING */ 4,
|
|
||||||
TelephonyManager.DATA_CONNECTED,
|
|
||||||
TelephonyManager.DATA_CONNECTING,
|
|
||||||
)
|
|
||||||
|
|
||||||
return dangerousMobileDataStates.contains(telephony.dataState)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the user associated with this context.
|
* Gets the user associated with this context.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -21,4 +21,4 @@
|
|||||||
android:pathData="M5.209,1.737C5.313,1.473 5.687,1.473 5.791,1.737L6.157,2.667C6.189,2.747 6.253,2.811 6.333,2.843L7.263,3.209C7.527,3.313 7.527,3.687 7.263,3.791L6.333,4.157C6.253,4.189 6.189,4.253 6.157,4.333L5.791,5.263C5.687,5.527 5.313,5.527 5.209,5.263L4.843,4.333C4.811,4.253 4.747,4.189 4.667,4.157L3.737,3.791C3.473,3.687 3.473,3.313 3.737,3.209L4.667,2.843C4.747,2.811 4.811,2.747 4.843,2.667L5.209,1.737Z" />
|
android:pathData="M5.209,1.737C5.313,1.473 5.687,1.473 5.791,1.737L6.157,2.667C6.189,2.747 6.253,2.811 6.333,2.843L7.263,3.209C7.527,3.313 7.527,3.687 7.263,3.791L6.333,4.157C6.253,4.189 6.189,4.253 6.157,4.333L5.791,5.263C5.687,5.527 5.313,5.527 5.209,5.263L4.843,4.333C4.811,4.253 4.747,4.189 4.667,4.157L3.737,3.791C3.473,3.687 3.473,3.313 3.737,3.209L4.667,2.843C4.747,2.811 4.811,2.747 4.843,2.667L5.209,1.737Z" />
|
||||||
</group>
|
</group>
|
||||||
</vector>
|
</vector>
|
||||||
aight
|
|
||||||
|
|||||||
@@ -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="#e8eaed"
|
||||||
|
android:pathData="M18,8h-1V6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2H6c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8H8.9V6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z" />
|
||||||
|
</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="#e8eaed"
|
||||||
|
android:pathData="M18,8h-1V6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6h1.9c0,-1.71 1.39,-3.1 3.1,-3.1s3.1,1.39 3.1,3.1v2H6c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V10c0,-1.1 -0.9,-2 -2,-2zM18,20H6V10h12v10zM12,17c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2z" />
|
||||||
|
</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,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>
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960">
|
|
||||||
<path
|
|
||||||
android:fillColor="#e8eaed"
|
|
||||||
android:pathData="M120,880v-800l60,60 60,-60 60,60 60,-60 60,60 60,-60 60,60 60,-60 60,60 60,-60 60,60 60,-60v800l-60,-60 -60,60 -60,-60 -60,60 -60,-60 -60,60 -60,-60 -60,60 -60,-60 -60,60 -60,-60 -60,60ZM240,680h480v-80L240,600v80ZM240,520h480v-80L240,440v80ZM240,360h480v-80L240,280v80Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960">
|
|
||||||
<path
|
|
||||||
android:fillColor="#e8eaed"
|
|
||||||
android:pathData="m819,932 l-59,-59q-10,3 -19.5,5T720,880L240,880q-50,0 -85,-35t-35,-85v-120h120v-287L27,140l57,-57L876,875l-57,57ZM840,727 L760,647v-447L313,200l-73,-73v-47l60,60 60,-60 60,60 60,-60 60,60 60,-60 60,60 60,-60 60,60 60,-60v647ZM320,640h207L320,433v207ZM475,360 L395,280h205v80L475,360ZM595,480 L515,400h85v80h-5ZM680,480q-17,0 -28.5,-11.5T640,440q0,-17 11.5,-28.5T680,400q17,0 28.5,11.5T720,440q0,17 -11.5,28.5T680,480ZM680,360q-17,0 -28.5,-11.5T640,320q0,-17 11.5,-28.5T680,280q17,0 28.5,11.5T720,320q0,17 -11.5,28.5T680,360Z" />
|
|
||||||
</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>
|
||||||
@@ -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="#e8eaed"
|
||||||
|
android:pathData="M3,17v2h6v-2H3zM3,5v2h10V5H3zM13,21v-2h8v-2h-8v-2h-2v6h2zM7,9v2H3v2h4v2h2V9H7zM21,13v-2H11v2h10zM15,7h2V5h4V3h-4V1h-2v6z" />
|
||||||
|
</vector>
|
||||||
@@ -5,5 +5,5 @@
|
|||||||
android:viewportHeight="960">
|
android:viewportHeight="960">
|
||||||
<path
|
<path
|
||||||
android:fillColor="#e8eaed"
|
android:fillColor="#e8eaed"
|
||||||
android:pathData="M480,840q-75,0 -140.5,-28.5t-114,-77q-48.5,-48.5 -77,-114T120,480q0,-75 28.5,-140.5t77,-114q48.5,-48.5 114,-77T480,120q82,0 155.5,35T760,254v-54q0,-17 11.5,-28.5T800,160q17,0 28.5,11.5T840,200v160q0,17 -11.5,28.5T800,400L640,400q-17,0 -28.5,-11.5T600,360q0,-17 11.5,-28.5T640,320h70q-41,-56 -101,-88t-129,-32q-117,0 -198.5,81.5T200,480q0,117 81.5,198.5T480,760q95,0 170,-57t99,-147q5,-16 18,-24t29,-6q17,2 27,14.5t6,27.5q-29,119 -126,195.5T480,840ZM520,464 L620,564q11,11 11,28t-11,28q-11,11 -28,11t-28,-11L452,508q-6,-6 -9,-13.5t-3,-15.5v-159q0,-17 11.5,-28.5T480,280q17,0 28.5,11.5T520,320v144Z" />
|
android:pathData="M480,640q17,0 28.5,-11.5T520,600v-184l64,64q11,11 28,11t28,-11q11,-11 11,-28t-11,-28L508,308q-12,-12 -28,-12t-28,12L324,424q-11,11 -11,28t11,28q11,11 28,11t28,-11l64,-64v184q0,17 11.5,28.5T480,640Zm0,240q-83,0 -156,-31.5T197,763q-54,-54 -85.5,-127T80,480q0,-83 31.5,-156T197,197q54,-54 127,-85.5T480,80q83,0 156,31.5T763,197q54,54 85.5,127T880,480q0,83 -31.5,156T763,763q-54,54 -127,85.5T480,880Z" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
<string name="action_retry_install">Retry installation</string>
|
<string name="action_retry_install">Retry installation</string>
|
||||||
<string name="action_open_error_log">Open error log</string>
|
<string name="action_open_error_log">Open error log</string>
|
||||||
<string name="action_apply">Apply</string>
|
<string name="action_apply">Apply</string>
|
||||||
|
<string name="action_done">Done</string>
|
||||||
<string name="action_confirm">Confirm</string>
|
<string name="action_confirm">Confirm</string>
|
||||||
<string name="action_dismiss">Dismiss</string>
|
<string name="action_dismiss">Dismiss</string>
|
||||||
<string name="action_install">Install</string>
|
<string name="action_install">Install</string>
|
||||||
@@ -43,10 +44,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>
|
||||||
@@ -63,7 +72,7 @@
|
|||||||
<string name="permissions_notifs_title">Notifications</string>
|
<string name="permissions_notifs_title">Notifications</string>
|
||||||
<string name="permissions_battery_title">Background Battery</string>
|
<string name="permissions_battery_title">Background Battery</string>
|
||||||
<string name="permissions_battery_desc">Ensures the installation process does not get automatically cancelled if the app is minimized.</string>
|
<string name="permissions_battery_desc">Ensures the installation process does not get automatically cancelled if the app is minimized.</string>
|
||||||
<string name="permissions_notifs_desc">Used only to show the download progress if Radiant Lyrics Manager is minimized during installation.</string>
|
<string name="permissions_notifs_desc">Used to notify you when updates are available!</string>
|
||||||
|
|
||||||
<string name="permissions_root_denied">Failed to obtain root permissions</string>
|
<string name="permissions_root_denied">Failed to obtain root permissions</string>
|
||||||
<string name="permissions_shizuku_denied">Failed to obtain Shizuku permissions</string>
|
<string name="permissions_shizuku_denied">Failed to obtain Shizuku permissions</string>
|
||||||
@@ -121,7 +130,6 @@
|
|||||||
|
|
||||||
<string name="navigation_back">Back</string>
|
<string name="navigation_back">Back</string>
|
||||||
<string name="navigation_about">About</string>
|
<string name="navigation_about">About</string>
|
||||||
<string name="navigation_logs">Logs</string>
|
|
||||||
<string name="navigation_settings">Settings</string>
|
<string name="navigation_settings">Settings</string>
|
||||||
<string name="navigation_refresh">Refresh</string>
|
<string name="navigation_refresh">Refresh</string>
|
||||||
|
|
||||||
@@ -235,15 +243,18 @@
|
|||||||
>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>
|
||||||
<string name="patchopts_patches_desc">Toggle which patches are applied during installation. Unchecked patches are skipped entirely.</string>
|
<string name="patchopts_patches_desc">Toggle which patches are applied during installation. Unchecked patches are skipped entirely.</string>
|
||||||
<string name="patchopts_patches_summary">%1$d of %2$d enabled</string>
|
<string name="patchopts_patches_summary">%1$d of %2$d enabled</string>
|
||||||
|
<string name="patchopts_advanced_button">Advanced Options</string>
|
||||||
|
<string name="patchopts_advanced_sheet_label">Advanced Options</string>
|
||||||
|
<string name="patchopts_toggle_snap">Toggle snapping to steps</string>
|
||||||
|
|
||||||
<string name="patch_lyrics_disable_cover_title">Disable Lyrics Cover</string>
|
<string name="patch_lyrics_disable_cover_title">Disable Lyrics Cover</string>
|
||||||
<string name="patch_lyrics_disable_cover_desc">Removes the mini track cover from the lyrics screen.</string>
|
<string name="patch_lyrics_disable_cover_desc">Removes the mini track cover from the lyrics screen.</string>
|
||||||
@@ -251,8 +262,22 @@
|
|||||||
<string name="patch_lyrics_progress_pill_desc">Restores the old Track Progress Pill in the top of the Lyrics screen!</string>
|
<string name="patch_lyrics_progress_pill_desc">Restores the old Track Progress Pill in the top of the Lyrics screen!</string>
|
||||||
<string name="patch_lyrics_replace_button_title">Replace Lyrics Button</string>
|
<string name="patch_lyrics_replace_button_title">Replace Lyrics Button</string>
|
||||||
<string name="patch_lyrics_replace_button_desc">Replaces the Lyrics button with the RL Sparkle!</string>
|
<string name="patch_lyrics_replace_button_desc">Replaces the Lyrics button with the RL Sparkle!</string>
|
||||||
|
<string name="patch_lyrics_rl_api_title">Radiant Lyrics API - WIP</string>
|
||||||
|
<string name="patch_lyrics_rl_api_desc">Use Radiant Lyrics API to fetch Lyrics (Higher quality & More providers)</string>
|
||||||
|
<string name="patch_sticky_lyrics_title">Sticky Lyrics</string>
|
||||||
|
<string name="patch_sticky_lyrics_desc">Always Forces the Lyrics page to be opened (aslong as the track has lyrics)</string>
|
||||||
|
<string name="patch_lyrics_keep_controls_title">Keep Controls Visible</string>
|
||||||
|
<string
|
||||||
|
name="patch_lyrics_keep_controls_desc"
|
||||||
|
>Inverts the auto-hide behavior on the lyrics screen, playback controls stay visible by default and only hide when you tap them off.</string>
|
||||||
<string name="patch_player_backdrop_title">Player Backdrop</string>
|
<string name="patch_player_backdrop_title">Player Backdrop</string>
|
||||||
<string name="patch_player_backdrop_desc">Restores the legacy translucent backdrop blur behind the player.</string>
|
<string name="patch_player_backdrop_desc">Restores the legacy translucent backdrop blur behind the player.</string>
|
||||||
|
<string name="patch_quality_badge_colors_title">Quality Badge Colors</string>
|
||||||
|
<string name="patch_quality_badge_colors_desc">Brings color back to the player quality badge.</string>
|
||||||
|
<string name="patch_player_one_handed_title">One-Handed Mode</string>
|
||||||
|
<string name="patch_player_one_handed_desc">Mirrors the bottom player buttons left-to-right for easier one-handed reach.</string>
|
||||||
|
<string name="patch_cover_everywhere_title">Cover Everywhere - WIP</string>
|
||||||
|
<string name="patch_cover_everywhere_desc">Applies the blurred backdrop to every Compose based page (Home & Collection)</string>
|
||||||
<string name="patch_lyrics_replace_share_button_title">Replace Share Button</string>
|
<string name="patch_lyrics_replace_share_button_title">Replace Share Button</string>
|
||||||
<string
|
<string
|
||||||
name="patch_lyrics_replace_share_button_desc"
|
name="patch_lyrics_replace_share_button_desc"
|
||||||
@@ -266,10 +291,49 @@
|
|||||||
name="patch_enable_legacy_ui_desc"
|
name="patch_enable_legacy_ui_desc"
|
||||||
>[This patch will stop working soon] - Replaces the New Compose based UI with the Legacy UI (disables player-market-ui feature flag)</string>
|
>[This patch will stop working soon] - Replaces the New Compose based UI with the Legacy UI (disables player-market-ui feature flag)</string>
|
||||||
|
|
||||||
|
<string name="patch_mini_player_redesign_title">Redesigned Mini-Player</string>
|
||||||
|
<string name="patch_mini_player_redesign_desc">Integrates the Seekbar into the control panel at the bottom of the screen.</string>
|
||||||
|
<string name="patch_mini_player_gestures_title">Mini-Player Gestures</string>
|
||||||
|
<string name="patch_mini_player_gestures_desc">Swipe up on the mini-player to open the full player.</string>
|
||||||
|
<string name="patch_mini_player_drag_title">Swipe Up Style</string>
|
||||||
|
<string name="patch_mini_player_drag_desc">Whether the player follows your finger when swiping up to open it.</string>
|
||||||
|
<string name="patch_mini_player_drag_static">Static</string>
|
||||||
|
<string name="patch_mini_player_drag_drag">Drag</string>
|
||||||
|
<string name="patch_mini_player_left_right_gestures_title">Next/Previous Gestures</string>
|
||||||
|
<string name="patch_mini_player_left_right_gestures_desc">Swipe left or right on the mini-player to skip tracks.</string>
|
||||||
|
<string name="patch_mini_player_dynamic_bg_title">Mini-Player Dynamic Background</string>
|
||||||
|
<string name="patch_mini_player_dynamic_bg_desc">Colorizes the Mini-Player depending on the currently playing track.</string>
|
||||||
|
<string name="patch_mini_player_variant_floating_title">Floating</string>
|
||||||
|
<string name="patch_mini_player_variant_square_grey_title">Grey</string>
|
||||||
|
<string name="patch_mini_player_variant_square_black_title">Black</string>
|
||||||
|
<string name="patch_mini_player_variant_legacy_title">Legacy</string>
|
||||||
|
<string name="patch_mini_player_border_title">Progress Border</string>
|
||||||
|
<string name="patch_mini_player_border_desc">Draws an animated progress ring around the floating mini-player. Requires the Floating style.</string>
|
||||||
|
|
||||||
|
<!-- Advanced option labels -->
|
||||||
|
<string name="patch_opt_backdrop_blur_title">Blur Strength</string>
|
||||||
|
<string name="patch_opt_backdrop_blur_desc">How strongly the album art behind the player is blurred.</string>
|
||||||
|
<string name="patch_opt_backdrop_dimming_title">Backdrop Dimming</string>
|
||||||
|
<string name="patch_opt_backdrop_dimming_desc">Adjusts the level of darkening on the backdrop to keep foreground controls legible.</string>
|
||||||
|
|
||||||
|
<string name="patch_lock_required_title">Patch Required!</string>
|
||||||
|
<string name="patch_lock_blocked_title">Patch Blocked!</string>
|
||||||
|
<string name="patch_lock_required_msg">Patch Required by <b>%s</b>.</string>
|
||||||
|
<string name="patch_lock_blocked_msg">Patch Blocked by <b>%s</b>.</string>
|
||||||
|
<string name="patch_opt_lock_title">Option Locked</string>
|
||||||
|
<string name="patch_opt_lock_needs_variant">Requires the <b>%s</b> style.</string>
|
||||||
|
<string name="patch_opt_lock_needs_option">Requires the <b>%s</b> option.</string>
|
||||||
|
<string name="action_got_it">Got it</string>
|
||||||
|
|
||||||
<string name="componentopts_screen_title">Custom Component (%s)</string>
|
<string name="componentopts_screen_title">Custom Component (%s)</string>
|
||||||
<string name="componentopts_screen_desc">Select a custom build that was imported by Manager.</string>
|
<string name="componentopts_screen_desc">Select a custom build that was imported by Manager.</string>
|
||||||
<string name="componentopts_selected_none">Latest</string>
|
<string name="componentopts_selected_none">Latest</string>
|
||||||
<string name="componentopts_deleted">Successfully deleted component!</string>
|
<string name="componentopts_deleted">Successfully deleted component!</string>
|
||||||
|
<string name="componentopts_browse_title">Browse files…</string>
|
||||||
|
<string name="componentopts_browse_desc">Import a file from your device</string>
|
||||||
|
<string name="componentopts_releases_title">GitHub Releases</string>
|
||||||
|
<string name="componentopts_releases_desc">Pick a historical release from the repo</string>
|
||||||
|
<string name="componentopts_releases_empty">No releases with this component found</string>
|
||||||
|
|
||||||
<string name="log_title">Log</string>
|
<string name="log_title">Log</string>
|
||||||
<string name="log_section_install_info">Installation Info</string>
|
<string name="log_section_install_info">Installation Info</string>
|
||||||
@@ -280,13 +344,6 @@
|
|||||||
<string name="log_action_export_apk">Export APK</string>
|
<string name="log_action_export_apk">Export APK</string>
|
||||||
<string name="log_action_share">Share log</string>
|
<string name="log_action_share">Share log</string>
|
||||||
|
|
||||||
<string name="logs_title">Logs</string>
|
|
||||||
<string name="logs_none">No logs present!</string>
|
|
||||||
<string name="logs_action_delete_all">Delete all logs</string>
|
|
||||||
<string name="logs_status_delete_success">Successfully deleted all logs</string>
|
|
||||||
<string name="logs_wipe_title">Clear Logs</string>
|
|
||||||
<string name="logs_wipe_desc">Are you sure you want to permanently delete all the logs?</string>
|
|
||||||
|
|
||||||
<string name="notif_group_install_title">Active installation</string>
|
<string name="notif_group_install_title">Active installation</string>
|
||||||
<string name="notif_group_install_desc">Progress notifications sent during a minimized installation</string>
|
<string name="notif_group_install_desc">Progress notifications sent during a minimized installation</string>
|
||||||
<string name="notif_install_ready_title">Radiant Lyrics installation ready!</string>
|
<string name="notif_install_ready_title">Radiant Lyrics installation ready!</string>
|
||||||
@@ -302,7 +359,7 @@
|
|||||||
<string name="play_protect_warning_open_gpp">Open Play Protect</string>
|
<string name="play_protect_warning_open_gpp">Open Play Protect</string>
|
||||||
<string name="play_protect_warning_disable">Don\'t show this again</string>
|
<string name="play_protect_warning_disable">Don\'t show this again</string>
|
||||||
|
|
||||||
<string name="fun_fact_prefix">Fun Fact: %s</string>
|
<string name="fun_fact_prefix">%s</string>
|
||||||
<string name="fun_fact_1">Did you know that TIDAL no longer like blur!</string>
|
<string name="fun_fact_1">Did you know that TIDAL no longer like blur!</string>
|
||||||
<string name="fun_fact_2">Radiant Lyrics is also available for DESKTOP!!!</string>
|
<string name="fun_fact_2">Radiant Lyrics is also available for DESKTOP!!!</string>
|
||||||
<string name="fun_fact_3">i am in your walls!</string>
|
<string name="fun_fact_3">i am in your walls!</string>
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
--- a/com/tidal/wave2/foundation/WaveScaffoldKt.smali
|
||||||
|
+++ b/com/tidal/wave2/foundation/WaveScaffoldKt.smali
|
||||||
|
@@ -889,6 +889,10 @@
|
||||||
|
.line 386
|
||||||
|
.line 387
|
||||||
|
:cond_20
|
||||||
|
+ const/4 v0, 0x0 # render $changed flag
|
||||||
|
+
|
||||||
|
+ invoke-static {v1, v0}, Lradiant/HomeBackdrop;->render(Landroidx/compose/runtime/Composer;I)V # render album-art backdrop
|
||||||
|
+
|
||||||
|
new-instance v0, Lcom/tidal/wave2/foundation/WaveScaffoldKt$WaveScaffold$2;
|
||||||
|
|
||||||
|
.line 388
|
||||||
|
@@ -991,7 +995,7 @@
|
||||||
|
|
||||||
|
.line 436
|
||||||
|
.line 437
|
||||||
|
- move-wide v15, v10
|
||||||
|
+ const-wide/16 v15, 0x0 # transparent containerColor
|
||||||
|
|
||||||
|
.line 438
|
||||||
|
const/4 v11, 0x0
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
--- a/com/tidal/android/feature/appscaffold/ui/p.smali
|
||||||
|
+++ b/com/tidal/android/feature/appscaffold/ui/p.smali
|
||||||
|
@@ -51,6 +51,8 @@
|
||||||
|
.line 10
|
||||||
|
iput-object p4, p0, Lcom/tidal/android/feature/appscaffold/ui/p;->d:Ljava/lang/String;
|
||||||
|
|
||||||
|
+ invoke-static {p3, p4}, Lradiant/HomeBackdrop;->fromMiniPlayer(ILjava/lang/String;)V # capture cover state
|
||||||
|
+
|
||||||
|
.line 11
|
||||||
|
.line 12
|
||||||
|
iput-boolean p5, p0, Lcom/tidal/android/feature/appscaffold/ui/p;->e:Z
|
||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"tidalVersionCode": 9089,
|
"tidalVersionCode": 9090,
|
||||||
"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.9.13"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +1,36 @@
|
|||||||
--- a/com/tidal/android/core/debug/DebugFeatureInteractorDefault.smali
|
--- a/com/tidal/android/core/debug/DebugFeatureInteractorDefault.smali
|
||||||
+++ b/com/tidal/android/core/debug/DebugFeatureInteractorDefault.smali
|
+++ b/com/tidal/android/core/debug/DebugFeatureInteractorDefault.smali
|
||||||
@@ -53,6 +53,11 @@
|
@@ -54,6 +54,9 @@
|
||||||
.method public final a()Z
|
.method public final a()Z
|
||||||
.locals 2
|
.locals 2
|
||||||
|
|
||||||
+ # rl-debug-unlock: force a() to always return true, bypassing the
|
+ const/4 v0, 0x1 # force true
|
||||||
+ # "debug-menu" feature flag check that normally hides the Settings entry.
|
+ return v0 # bypass debug flag
|
||||||
+ const/4 v0, 0x1
|
|
||||||
+ return v0
|
|
||||||
+
|
+
|
||||||
.line 1
|
.line 1
|
||||||
iget-object v0, p0, Lcom/tidal/android/core/debug/DebugFeatureInteractorDefault;->a:Lcom/tidal/android/featureflags/l;
|
iget-object v0, p0, Lcom/tidal/android/core/debug/DebugFeatureInteractorDefault;->a:Lcom/tidal/android/featureflags/l;
|
||||||
|
|
||||||
@@ -97,6 +102,14 @@
|
@@ -98,6 +101,14 @@
|
||||||
}
|
}
|
||||||
.end annotation
|
.end annotation
|
||||||
|
|
||||||
+ # rl-debug-unlock: short-circuit b() to always emit MutableStateFlow(TRUE).
|
+ sget-object v0, Ljava/lang/Boolean;->TRUE:Ljava/lang/Boolean; # always-true value
|
||||||
+ # Mirrors the original ":cond_1 :goto_0" path the app uses for internal/test builds.
|
|
||||||
+ sget-object v0, Ljava/lang/Boolean;->TRUE:Ljava/lang/Boolean;
|
|
||||||
+
|
+
|
||||||
+ invoke-static {v0}, Lkotlinx/coroutines/flow/StateFlowKt;->MutableStateFlow(Ljava/lang/Object;)Lkotlinx/coroutines/flow/MutableStateFlow;
|
+ invoke-static {v0}, Lkotlinx/coroutines/flow/StateFlowKt;->MutableStateFlow(Ljava/lang/Object;)Lkotlinx/coroutines/flow/MutableStateFlow; # wrap in flow
|
||||||
+
|
+
|
||||||
+ move-result-object v0
|
+ move-result-object v0 # the flow
|
||||||
+
|
+
|
||||||
+ return-object v0
|
+ return-object v0 # short-circuit b()
|
||||||
+
|
+
|
||||||
.line 1
|
.line 1
|
||||||
sget-object v0, Lh50/a;->a:Ljava/lang/String;
|
sget-object v0, Lj50/a;->a:Ljava/lang/String;
|
||||||
|
|
||||||
@@ -261,6 +274,11 @@
|
@@ -261,5 +272,8 @@
|
||||||
.method public final c()Z
|
.method public final c()Z
|
||||||
.locals 3
|
.locals 3
|
||||||
|
|
||||||
+ # rl-debug-unlock: force c() to always return true, bypassing the
|
+ const/4 v0, 0x1 # force true
|
||||||
+ # applicationId-substring / in-app-bug-reports / export-logs flag checks.
|
+ return v0 # bypass debug flag
|
||||||
+ const/4 v0, 0x1
|
|
||||||
+ return v0
|
|
||||||
+
|
+
|
||||||
.line 1
|
.line 1
|
||||||
sget-object v0, Lh50/a;->a:Ljava/lang/String;
|
sget-object v0, Lj50/a;->a:Ljava/lang/String;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
--- a/ig/d.smali
|
--- a/kg/e.smali
|
||||||
+++ b/ig/d.smali
|
+++ b/kg/e.smali
|
||||||
@@ -26,8 +26,8 @@
|
@@ -26,8 +26,8 @@
|
||||||
new-instance v0, Lig/d;
|
new-instance v0, Lkg/e;
|
||||||
|
|
||||||
.line 2
|
.line 2
|
||||||
.line 3
|
.line 3
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
.class public final Lcom/tidal/android/feature/appscaffold/ui/q$c;
|
||||||
|
.super Lcom/tidal/android/feature/appscaffold/ui/q;
|
||||||
|
.source "SourceFile"
|
||||||
|
|
||||||
|
# Synthetic mini-player event used by right swipe (previous/rewind).
|
||||||
|
# TIDAL already exposes q$a for next in this path
|
||||||
|
|
||||||
|
# annotations
|
||||||
|
.annotation system Ldalvik/annotation/EnclosingClass;
|
||||||
|
value = Lcom/tidal/android/feature/appscaffold/ui/q;
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
.annotation system Ldalvik/annotation/InnerClass;
|
||||||
|
accessFlags = 0x19
|
||||||
|
name = "c"
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
|
||||||
|
# static fields
|
||||||
|
.field public static final a:Lcom/tidal/android/feature/appscaffold/ui/q$c;
|
||||||
|
|
||||||
|
|
||||||
|
# direct methods
|
||||||
|
.method static constructor <clinit>()V
|
||||||
|
.locals 1
|
||||||
|
|
||||||
|
new-instance v0, Lcom/tidal/android/feature/appscaffold/ui/q$c;
|
||||||
|
|
||||||
|
invoke-direct {v0}, Lcom/tidal/android/feature/appscaffold/ui/q;-><init>()V
|
||||||
|
|
||||||
|
sput-object v0, Lcom/tidal/android/feature/appscaffold/ui/q$c;->a:Lcom/tidal/android/feature/appscaffold/ui/q$c;
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
# virtual methods
|
||||||
|
.method public final equals(Ljava/lang/Object;)Z
|
||||||
|
.locals 1
|
||||||
|
|
||||||
|
const/4 v0, 0x1
|
||||||
|
|
||||||
|
if-ne p0, p1, :cond_0
|
||||||
|
|
||||||
|
return v0
|
||||||
|
|
||||||
|
:cond_0
|
||||||
|
instance-of p1, p1, Lcom/tidal/android/feature/appscaffold/ui/q$c;
|
||||||
|
|
||||||
|
if-nez p1, :cond_1
|
||||||
|
|
||||||
|
const/4 p1, 0x0
|
||||||
|
|
||||||
|
return p1
|
||||||
|
|
||||||
|
:cond_1
|
||||||
|
return v0
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public final hashCode()I
|
||||||
|
.locals 1
|
||||||
|
|
||||||
|
const v0, -0x5d6a0f8
|
||||||
|
|
||||||
|
return v0
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public final toString()Ljava/lang/String;
|
||||||
|
.locals 1
|
||||||
|
|
||||||
|
const-string v0, "PreviousButtonClicked"
|
||||||
|
|
||||||
|
return-object v0
|
||||||
|
.end method
|
||||||
@@ -0,0 +1,370 @@
|
|||||||
|
.class public final Lradiant/HomeBackdrop;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
|
||||||
|
|
||||||
|
# static fields
|
||||||
|
.field public static volatile currentAlbumId:I
|
||||||
|
|
||||||
|
.field public static coverUuidState:Landroidx/compose/runtime/MutableState;
|
||||||
|
.annotation system Ldalvik/annotation/Signature;
|
||||||
|
value = {
|
||||||
|
"Landroidx/compose/runtime/MutableState<",
|
||||||
|
"Ljava/lang/String;",
|
||||||
|
">;"
|
||||||
|
}
|
||||||
|
.end annotation
|
||||||
|
.end field
|
||||||
|
|
||||||
|
|
||||||
|
# direct methods
|
||||||
|
.method static constructor <clinit>()V
|
||||||
|
.locals 4
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
sput v0, Lradiant/HomeBackdrop;->currentAlbumId:I
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
const/4 v1, 0x0
|
||||||
|
|
||||||
|
const/4 v2, 0x2
|
||||||
|
|
||||||
|
const/4 v3, 0x0
|
||||||
|
|
||||||
|
invoke-static {v0, v1, v2, v3}, Landroidx/compose/runtime/SnapshotStateKt;->mutableStateOf$default(Ljava/lang/Object;Landroidx/compose/runtime/SnapshotMutationPolicy;ILjava/lang/Object;)Landroidx/compose/runtime/MutableState;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
sput-object v0, Lradiant/HomeBackdrop;->coverUuidState:Landroidx/compose/runtime/MutableState;
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
.method private constructor <init>()V
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
.method public static dlog(Ljava/lang/String;)V
|
||||||
|
.locals 1
|
||||||
|
|
||||||
|
const-string v0, "RLHomeBackdrop"
|
||||||
|
|
||||||
|
invoke-static {v0, p0}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
.method private static isDebugMenuStack()Z
|
||||||
|
.locals 6
|
||||||
|
|
||||||
|
new-instance v0, Ljava/lang/Throwable;
|
||||||
|
|
||||||
|
invoke-direct {v0}, Ljava/lang/Throwable;-><init>()V
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Ljava/lang/Throwable;->getStackTrace()[Ljava/lang/StackTraceElement;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
array-length v1, v0
|
||||||
|
|
||||||
|
const/4 v2, 0x0
|
||||||
|
|
||||||
|
:loop_start
|
||||||
|
if-ge v2, v1, :loop_end
|
||||||
|
|
||||||
|
aget-object v3, v0, v2
|
||||||
|
|
||||||
|
invoke-virtual {v3}, Ljava/lang/StackTraceElement;->getClassName()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v3
|
||||||
|
|
||||||
|
const-string v4, "debugmenu"
|
||||||
|
|
||||||
|
invoke-virtual {v3, v4}, Ljava/lang/String;->contains(Ljava/lang/CharSequence;)Z
|
||||||
|
|
||||||
|
move-result v5
|
||||||
|
|
||||||
|
if-eqz v5, :next
|
||||||
|
|
||||||
|
const/4 v0, 0x1
|
||||||
|
|
||||||
|
return v0
|
||||||
|
|
||||||
|
:next
|
||||||
|
add-int/lit8 v2, v2, 0x1
|
||||||
|
|
||||||
|
goto :loop_start
|
||||||
|
|
||||||
|
:loop_end
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
return v0
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
.method public static fromMiniPlayer(ILjava/lang/String;)V
|
||||||
|
.locals 2
|
||||||
|
|
||||||
|
new-instance v0, Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
const-string v1, "fromMiniPlayer: id="
|
||||||
|
|
||||||
|
invoke-direct {v0, v1}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
invoke-virtual {v0, p0}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
const-string v1, " cover="
|
||||||
|
|
||||||
|
invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v0, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
invoke-static {v0}, Lradiant/HomeBackdrop;->dlog(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
sput p0, Lradiant/HomeBackdrop;->currentAlbumId:I
|
||||||
|
|
||||||
|
sget-object v0, Lradiant/HomeBackdrop;->coverUuidState:Landroidx/compose/runtime/MutableState;
|
||||||
|
|
||||||
|
invoke-interface {v0, p1}, Landroidx/compose/runtime/MutableState;->setValue(Ljava/lang/Object;)V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
.method public static onTrack(Lcom/aspiro/wamp/model/Track;)V
|
||||||
|
.locals 3
|
||||||
|
|
||||||
|
const-string v0, "onTrack: entered"
|
||||||
|
|
||||||
|
invoke-static {v0}, Lradiant/HomeBackdrop;->dlog(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
if-eqz p0, :clear
|
||||||
|
|
||||||
|
:try_start
|
||||||
|
invoke-virtual {p0}, Lcom/aspiro/wamp/model/MediaItem;->getAlbum()Lcom/aspiro/wamp/model/Album;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
if-eqz v0, :clear
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Lcom/aspiro/wamp/model/Album;->getId()I
|
||||||
|
|
||||||
|
move-result v1
|
||||||
|
|
||||||
|
sput v1, Lradiant/HomeBackdrop;->currentAlbumId:I
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Lcom/aspiro/wamp/model/Album;->getCover()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
sget-object v1, Lradiant/HomeBackdrop;->coverUuidState:Landroidx/compose/runtime/MutableState;
|
||||||
|
|
||||||
|
invoke-interface {v1, v0}, Landroidx/compose/runtime/MutableState;->setValue(Ljava/lang/Object;)V
|
||||||
|
|
||||||
|
new-instance v1, Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
const-string v2, "onTrack: set uuid="
|
||||||
|
|
||||||
|
invoke-direct {v1, v2}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
invoke-virtual {v1, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
invoke-static {v0}, Lradiant/HomeBackdrop;->dlog(Ljava/lang/String;)V
|
||||||
|
:try_end
|
||||||
|
.catch Ljava/lang/Throwable; {:try_start .. :try_end} :swallow
|
||||||
|
|
||||||
|
return-void
|
||||||
|
|
||||||
|
:clear
|
||||||
|
const-string v0, "onTrack: clearing (null track/album)"
|
||||||
|
|
||||||
|
invoke-static {v0}, Lradiant/HomeBackdrop;->dlog(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
sput v0, Lradiant/HomeBackdrop;->currentAlbumId:I
|
||||||
|
|
||||||
|
sget-object v1, Lradiant/HomeBackdrop;->coverUuidState:Landroidx/compose/runtime/MutableState;
|
||||||
|
|
||||||
|
const/4 v2, 0x0
|
||||||
|
|
||||||
|
invoke-interface {v1, v2}, Landroidx/compose/runtime/MutableState;->setValue(Ljava/lang/Object;)V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
|
||||||
|
:swallow
|
||||||
|
const-string v0, "onTrack: caught throwable"
|
||||||
|
|
||||||
|
invoke-static {v0}, Lradiant/HomeBackdrop;->dlog(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
.method public static render(Landroidx/compose/runtime/Composer;I)V
|
||||||
|
.locals 16
|
||||||
|
.annotation build Landroidx/compose/runtime/Composable;
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
invoke-static {}, Lradiant/HomeBackdrop;->isDebugMenuStack()Z
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
if-eqz v0, :proceed
|
||||||
|
|
||||||
|
return-void
|
||||||
|
|
||||||
|
:proceed
|
||||||
|
move-object/from16 v0, p0
|
||||||
|
|
||||||
|
const-string v1, "render: entered"
|
||||||
|
|
||||||
|
invoke-static {v1}, Lradiant/HomeBackdrop;->dlog(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
const v1, 0x52414449
|
||||||
|
|
||||||
|
invoke-interface {v0, v1}, Landroidx/compose/runtime/Composer;->startReplaceGroup(I)V
|
||||||
|
|
||||||
|
sget-object v1, Lradiant/HomeBackdrop;->coverUuidState:Landroidx/compose/runtime/MutableState;
|
||||||
|
|
||||||
|
invoke-interface {v1}, Landroidx/compose/runtime/State;->getValue()Ljava/lang/Object;
|
||||||
|
|
||||||
|
move-result-object v1
|
||||||
|
|
||||||
|
check-cast v1, Ljava/lang/String;
|
||||||
|
|
||||||
|
new-instance v2, Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
const-string v3, "render: uuid="
|
||||||
|
|
||||||
|
invoke-direct {v2, v3}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
invoke-virtual {v2, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v2}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v2
|
||||||
|
|
||||||
|
invoke-static {v2}, Lradiant/HomeBackdrop;->dlog(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
if-eqz v1, :skip
|
||||||
|
|
||||||
|
invoke-virtual {v1}, Ljava/lang/String;->length()I
|
||||||
|
|
||||||
|
move-result v2
|
||||||
|
|
||||||
|
if-eqz v2, :skip
|
||||||
|
|
||||||
|
sget v2, Lradiant/HomeBackdrop;->currentAlbumId:I
|
||||||
|
|
||||||
|
new-instance v3, Lcom/tidal/android/feature/playerscreen/ui/composables/n0;
|
||||||
|
|
||||||
|
invoke-direct {v3, v2, v1}, Lcom/tidal/android/feature/playerscreen/ui/composables/n0;-><init>(ILjava/lang/String;)V
|
||||||
|
|
||||||
|
sget-object v4, Landroidx/compose/ui/Modifier;->Companion:Landroidx/compose/ui/Modifier$Companion;
|
||||||
|
|
||||||
|
const/4 v5, 0x0
|
||||||
|
|
||||||
|
const/4 v6, 0x1
|
||||||
|
|
||||||
|
const/4 v7, 0x0
|
||||||
|
|
||||||
|
invoke-static {v4, v5, v6, v7}, Landroidx/compose/foundation/layout/SizeKt;->fillMaxSize$default(Landroidx/compose/ui/Modifier;FILjava/lang/Object;)Landroidx/compose/ui/Modifier;
|
||||||
|
|
||||||
|
move-result-object v4
|
||||||
|
|
||||||
|
const/high16 v5, 0x42200000
|
||||||
|
|
||||||
|
invoke-static {v5}, Landroidx/compose/ui/unit/Dp;->constructor-impl(F)F
|
||||||
|
|
||||||
|
move-result v5
|
||||||
|
|
||||||
|
sget-object v6, Landroidx/compose/ui/draw/BlurredEdgeTreatment;->Companion:Landroidx/compose/ui/draw/BlurredEdgeTreatment$Companion;
|
||||||
|
|
||||||
|
invoke-virtual {v6}, Landroidx/compose/ui/draw/BlurredEdgeTreatment$Companion;->getRectangle---Goahg()Landroidx/compose/ui/graphics/Shape;
|
||||||
|
|
||||||
|
move-result-object v6
|
||||||
|
|
||||||
|
invoke-static {v4, v5, v6}, Landroidx/compose/ui/draw/BlurKt;->blur-F8QBwvs(Landroidx/compose/ui/Modifier;FLandroidx/compose/ui/graphics/Shape;)Landroidx/compose/ui/Modifier;
|
||||||
|
|
||||||
|
move-result-object v4
|
||||||
|
|
||||||
|
sget-object v5, Landroidx/compose/ui/layout/ContentScale;->Companion:Landroidx/compose/ui/layout/ContentScale$Companion;
|
||||||
|
|
||||||
|
invoke-virtual {v5}, Landroidx/compose/ui/layout/ContentScale$Companion;->getCrop()Landroidx/compose/ui/layout/ContentScale;
|
||||||
|
|
||||||
|
move-result-object v5
|
||||||
|
|
||||||
|
move-object/from16 v6, v3
|
||||||
|
|
||||||
|
const/4 v7, 0x0
|
||||||
|
|
||||||
|
move-object/from16 v8, v4
|
||||||
|
|
||||||
|
const/4 v9, 0x0
|
||||||
|
|
||||||
|
move-object/from16 v10, v5
|
||||||
|
|
||||||
|
move-object/from16 v11, v1 # uuid as cache key
|
||||||
|
|
||||||
|
const/4 v12, 0x0
|
||||||
|
|
||||||
|
move-object/from16 v13, v0
|
||||||
|
|
||||||
|
const/4 v14, 0x0
|
||||||
|
|
||||||
|
const/16 v15, 0x48
|
||||||
|
|
||||||
|
invoke-static/range {v6 .. v15}, Lxd0/f;->a(Lyl0/l;Ljava/lang/String;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/graphics/ColorFilter;Landroidx/compose/ui/layout/ContentScale;Ljava/lang/Object;Lyl0/a;Landroidx/compose/runtime/Composer;II)V
|
||||||
|
|
||||||
|
sget-object v3, Landroidx/compose/ui/Modifier;->Companion:Landroidx/compose/ui/Modifier$Companion;
|
||||||
|
|
||||||
|
const/4 v4, 0x0
|
||||||
|
|
||||||
|
const/4 v5, 0x1
|
||||||
|
|
||||||
|
const/4 v6, 0x0
|
||||||
|
|
||||||
|
invoke-static {v3, v4, v5, v6}, Landroidx/compose/foundation/layout/SizeKt;->fillMaxSize$default(Landroidx/compose/ui/Modifier;FILjava/lang/Object;)Landroidx/compose/ui/Modifier;
|
||||||
|
|
||||||
|
move-result-object v3
|
||||||
|
|
||||||
|
const v4, -0x80000000
|
||||||
|
|
||||||
|
invoke-static {v4}, Landroidx/compose/ui/graphics/ColorKt;->Color(I)J
|
||||||
|
|
||||||
|
move-result-wide v4
|
||||||
|
|
||||||
|
invoke-static {}, Landroidx/compose/ui/graphics/RectangleShapeKt;->getRectangleShape()Landroidx/compose/ui/graphics/Shape;
|
||||||
|
|
||||||
|
move-result-object v6
|
||||||
|
|
||||||
|
invoke-static {v3, v4, v5, v6}, Landroidx/compose/foundation/BackgroundKt;->background-bw27NRU(Landroidx/compose/ui/Modifier;JLandroidx/compose/ui/graphics/Shape;)Landroidx/compose/ui/Modifier;
|
||||||
|
|
||||||
|
move-result-object v3
|
||||||
|
|
||||||
|
const/4 v4, 0x0
|
||||||
|
|
||||||
|
invoke-static {v3, v0, v4}, Landroidx/compose/foundation/layout/SpacerKt;->Spacer(Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;I)V
|
||||||
|
|
||||||
|
:skip
|
||||||
|
invoke-interface {v0}, Landroidx/compose/runtime/Composer;->endReplaceGroup()V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
.class public final Lradiant/MiniPlayerBackground;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
|
||||||
|
|
||||||
|
# static fields
|
||||||
|
.field public static final colorState:Landroidx/compose/runtime/MutableState; # player bg color
|
||||||
|
|
||||||
|
|
||||||
|
# direct methods
|
||||||
|
.method static constructor <clinit>()V
|
||||||
|
.locals 2
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
const/4 v1, 0x2
|
||||||
|
|
||||||
|
invoke-static {v0, v0, v1, v0}, Landroidx/compose/runtime/SnapshotStateKt;->mutableStateOf$default(Ljava/lang/Object;Landroidx/compose/runtime/SnapshotMutationPolicy;ILjava/lang/Object;)Landroidx/compose/runtime/MutableState;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
sput-object v0, Lradiant/MiniPlayerBackground;->colorState:Landroidx/compose/runtime/MutableState;
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
.method private constructor <init>()V
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
.method public static color(JLandroidx/compose/runtime/Composer;I)J
|
||||||
|
.locals 8
|
||||||
|
.annotation build Landroidx/compose/runtime/Composable;
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
const v0, 0x4d504442 # compose group key
|
||||||
|
|
||||||
|
invoke-interface {p2, v0}, Landroidx/compose/runtime/Composer;->startReplaceGroup(I)V
|
||||||
|
|
||||||
|
sget-object v0, Lradiant/MiniPlayerBackground;->colorState:Landroidx/compose/runtime/MutableState;
|
||||||
|
|
||||||
|
invoke-interface {v0}, Landroidx/compose/runtime/MutableState;->getValue()Ljava/lang/Object; # read player color
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
check-cast v0, Landroidx/compose/ui/graphics/Color;
|
||||||
|
|
||||||
|
if-eqz v0, :fallback
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Landroidx/compose/ui/graphics/Color;->unbox-impl()J
|
||||||
|
|
||||||
|
move-result-wide p0
|
||||||
|
|
||||||
|
:fallback
|
||||||
|
const/16 v0, 0x2ee # 750ms
|
||||||
|
|
||||||
|
const/4 v1, 0x0
|
||||||
|
|
||||||
|
const/4 v2, 0x0
|
||||||
|
|
||||||
|
const/4 v3, 0x6
|
||||||
|
|
||||||
|
const/4 v4, 0x0
|
||||||
|
|
||||||
|
invoke-static {v0, v1, v2, v3, v4}, Landroidx/compose/animation/core/AnimationSpecKt;->tween$default(IILandroidx/compose/animation/core/Easing;ILjava/lang/Object;)Landroidx/compose/animation/core/TweenSpec;
|
||||||
|
|
||||||
|
move-result-object v2
|
||||||
|
|
||||||
|
move-wide v0, p0
|
||||||
|
|
||||||
|
const-string v3, "miniPlayerBackgroundColorAnimation"
|
||||||
|
|
||||||
|
const/4 v4, 0x0
|
||||||
|
|
||||||
|
move-object v5, p2
|
||||||
|
|
||||||
|
const/16 v6, 0x1b0
|
||||||
|
|
||||||
|
const/16 v7, 0x8
|
||||||
|
|
||||||
|
invoke-static/range {v0 .. v7}, Landroidx/compose/animation/SingleValueAnimationKt;->animateColorAsState-euL9pac(JLandroidx/compose/animation/core/AnimationSpec;Ljava/lang/String;Lyl0/l;Landroidx/compose/runtime/Composer;II)Landroidx/compose/runtime/State;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
invoke-interface {v0}, Landroidx/compose/runtime/State;->getValue()Ljava/lang/Object;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
check-cast v0, Landroidx/compose/ui/graphics/Color;
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Landroidx/compose/ui/graphics/Color;->unbox-impl()J
|
||||||
|
|
||||||
|
move-result-wide p0
|
||||||
|
|
||||||
|
invoke-interface {p2}, Landroidx/compose/runtime/Composer;->endReplaceGroup()V
|
||||||
|
|
||||||
|
return-wide p0
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
.method public static setHex(Ljava/lang/String;)V
|
||||||
|
.locals 3
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
if-eqz p0, :set_color
|
||||||
|
|
||||||
|
:try_start
|
||||||
|
invoke-static {p0}, Landroid/graphics/Color;->parseColor(Ljava/lang/String;)I
|
||||||
|
|
||||||
|
move-result p0
|
||||||
|
|
||||||
|
invoke-static {p0}, Landroidx/compose/ui/graphics/ColorKt;->Color(I)J # pack ARGB color
|
||||||
|
|
||||||
|
move-result-wide v1
|
||||||
|
|
||||||
|
invoke-static {v1, v2}, Landroidx/compose/ui/graphics/Color;->box-impl(J)Landroidx/compose/ui/graphics/Color;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
:try_end
|
||||||
|
.catch Ljava/lang/Throwable; {:try_start .. :try_end} :catch_parse
|
||||||
|
|
||||||
|
goto :set_color
|
||||||
|
|
||||||
|
:catch_parse
|
||||||
|
move-exception p0
|
||||||
|
|
||||||
|
:set_color
|
||||||
|
sget-object p0, Lradiant/MiniPlayerBackground;->colorState:Landroidx/compose/runtime/MutableState;
|
||||||
|
|
||||||
|
invoke-interface {p0, v0}, Landroidx/compose/runtime/MutableState;->setValue(Ljava/lang/Object;)V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
.class public final Lradiant/MiniPlayerGestures$ApplyPending;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
.implements Ljava/lang/Runnable;
|
||||||
|
|
||||||
|
# annotations
|
||||||
|
.annotation system Ldalvik/annotation/EnclosingClass;
|
||||||
|
value = Lradiant/MiniPlayerGestures;
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
.annotation system Ldalvik/annotation/InnerClass;
|
||||||
|
accessFlags = 0x19
|
||||||
|
name = "ApplyPending"
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
|
||||||
|
# instance fields
|
||||||
|
.field public final a:Landroidx/compose/material3/SheetState;
|
||||||
|
|
||||||
|
.field public final b:I
|
||||||
|
|
||||||
|
|
||||||
|
# direct methods
|
||||||
|
.method public constructor <init>(Landroidx/compose/material3/SheetState;I)V
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||||
|
|
||||||
|
iput-object p1, p0, Lradiant/MiniPlayerGestures$ApplyPending;->a:Landroidx/compose/material3/SheetState;
|
||||||
|
|
||||||
|
iput p2, p0, Lradiant/MiniPlayerGestures$ApplyPending;->b:I
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
# virtual methods
|
||||||
|
# Apply once, then re schedule if there is still pending movement
|
||||||
|
# -> Sheet anchors can appear one (or more) frames after opening the player, so this keeps applying the latest drag until no delta remains
|
||||||
|
.method public final run()V
|
||||||
|
.locals 3
|
||||||
|
|
||||||
|
iget-object v0, p0, Lradiant/MiniPlayerGestures$ApplyPending;->a:Landroidx/compose/material3/SheetState;
|
||||||
|
|
||||||
|
invoke-static {v0}, Lradiant/MiniPlayerGestures;->applyPendingDrag(Landroidx/compose/material3/SheetState;)V
|
||||||
|
|
||||||
|
iget v1, p0, Lradiant/MiniPlayerGestures$ApplyPending;->b:I
|
||||||
|
|
||||||
|
add-int/lit8 v1, v1, -0x1
|
||||||
|
|
||||||
|
if-lez v1, :done
|
||||||
|
|
||||||
|
invoke-static {v0}, Lradiant/MiniPlayerGestures;->needsApply(Landroidx/compose/material3/SheetState;)Z
|
||||||
|
|
||||||
|
move-result v2
|
||||||
|
|
||||||
|
if-eqz v2, :done
|
||||||
|
|
||||||
|
invoke-static {v0, v1}, Lradiant/MiniPlayerGestures;->scheduleApply(Landroidx/compose/material3/SheetState;I)V
|
||||||
|
|
||||||
|
:done
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
.class public final Lradiant/MiniPlayerGestures$FeedbackLayer;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
.implements Lyl0/l;
|
||||||
|
|
||||||
|
|
||||||
|
# annotations
|
||||||
|
.annotation system Ldalvik/annotation/EnclosingClass;
|
||||||
|
value = Lradiant/MiniPlayerGestures;
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
.annotation system Ldalvik/annotation/InnerClass;
|
||||||
|
accessFlags = 0x19
|
||||||
|
name = "FeedbackLayer"
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
|
||||||
|
# static fields
|
||||||
|
.field public static final INSTANCE:Lradiant/MiniPlayerGestures$FeedbackLayer;
|
||||||
|
|
||||||
|
|
||||||
|
# direct methods
|
||||||
|
.method static constructor <clinit>()V
|
||||||
|
.locals 1
|
||||||
|
|
||||||
|
new-instance v0, Lradiant/MiniPlayerGestures$FeedbackLayer;
|
||||||
|
|
||||||
|
invoke-direct {v0}, Lradiant/MiniPlayerGestures$FeedbackLayer;-><init>()V
|
||||||
|
|
||||||
|
sput-object v0, Lradiant/MiniPlayerGestures$FeedbackLayer;->INSTANCE:Lradiant/MiniPlayerGestures$FeedbackLayer;
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method private constructor <init>()V
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
# virtual methods
|
||||||
|
.method public final invoke(Landroidx/compose/ui/graphics/GraphicsLayerScope;)Lkotlin/u;
|
||||||
|
.locals 1
|
||||||
|
|
||||||
|
sget-object v0, Lradiant/MiniPlayerGestures;->v:Landroidx/compose/runtime/MutableFloatState;
|
||||||
|
|
||||||
|
invoke-interface {v0}, Landroidx/compose/runtime/MutableFloatState;->getFloatValue()F
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
invoke-interface {p1, v0}, Landroidx/compose/ui/graphics/GraphicsLayerScope;->setTranslationY(F)V
|
||||||
|
|
||||||
|
sget-object p1, Lkotlin/u;->a:Lkotlin/u;
|
||||||
|
|
||||||
|
return-object p1
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public bridge synthetic invoke(Ljava/lang/Object;)Ljava/lang/Object;
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
check-cast p1, Landroidx/compose/ui/graphics/GraphicsLayerScope;
|
||||||
|
|
||||||
|
invoke-virtual {p0, p1}, Lradiant/MiniPlayerGestures$FeedbackLayer;->invoke(Landroidx/compose/ui/graphics/GraphicsLayerScope;)Lkotlin/u;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
|
||||||
|
return-object p1
|
||||||
|
.end method
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
.class public final Lradiant/MiniPlayerGestures$FeedbackResetAnimator;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
.implements Landroid/animation/ValueAnimator$AnimatorUpdateListener;
|
||||||
|
|
||||||
|
|
||||||
|
# annotations
|
||||||
|
.annotation system Ldalvik/annotation/EnclosingClass;
|
||||||
|
value = Lradiant/MiniPlayerGestures;
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
.annotation system Ldalvik/annotation/InnerClass;
|
||||||
|
accessFlags = 0x19
|
||||||
|
name = "FeedbackResetAnimator"
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
|
||||||
|
# direct methods
|
||||||
|
.method public constructor <init>()V
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# virtual methods
|
||||||
|
.method public final onAnimationUpdate(Landroid/animation/ValueAnimator;)V
|
||||||
|
.locals 3
|
||||||
|
|
||||||
|
move-object v0, p1
|
||||||
|
|
||||||
|
invoke-virtual {p1}, Landroid/animation/ValueAnimator;->getAnimatedValue()Ljava/lang/Object;
|
||||||
|
|
||||||
|
move-result-object v1
|
||||||
|
|
||||||
|
check-cast v1, Ljava/lang/Float;
|
||||||
|
|
||||||
|
invoke-virtual {v1}, Ljava/lang/Float;->floatValue()F
|
||||||
|
|
||||||
|
move-result v1
|
||||||
|
|
||||||
|
invoke-static {v1}, Lradiant/MiniPlayerGestures;->setSwipeFeedbackDirect(F)V
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Landroid/animation/ValueAnimator;->getAnimatedFraction()F
|
||||||
|
|
||||||
|
move-result v1
|
||||||
|
|
||||||
|
const/high16 v2, 0x3f800000 # 1.0f
|
||||||
|
|
||||||
|
cmpg-float v1, v1, v2
|
||||||
|
|
||||||
|
if-gez v1, :done
|
||||||
|
|
||||||
|
invoke-static {v0}, Lradiant/MiniPlayerGestures;->clearSwipeFeedbackAnimator(Landroid/animation/ValueAnimator;)V
|
||||||
|
|
||||||
|
:done
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
@@ -0,0 +1,521 @@
|
|||||||
|
.class public final Lradiant/MiniPlayerGestures$Gesture;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
.implements Lyl0/l;
|
||||||
|
|
||||||
|
# annotations
|
||||||
|
.annotation system Ldalvik/annotation/EnclosingClass;
|
||||||
|
value = Lradiant/MiniPlayerGestures;
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
.annotation system Ldalvik/annotation/InnerClass;
|
||||||
|
accessFlags = 0x19
|
||||||
|
name = "Gesture"
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
|
||||||
|
# instance fields
|
||||||
|
.field public final a:Lyl0/l;
|
||||||
|
|
||||||
|
# Y at ACTION_DOWN
|
||||||
|
.field public b:F
|
||||||
|
|
||||||
|
# Last Y seen (on this pointer stream)
|
||||||
|
.field public c:F
|
||||||
|
|
||||||
|
# True while this listener owns an active pointer stream
|
||||||
|
.field public d:Z
|
||||||
|
|
||||||
|
# True when sheet drag has started
|
||||||
|
.field public e:Z
|
||||||
|
|
||||||
|
.field public final f:Landroidx/compose/material3/SheetState;
|
||||||
|
|
||||||
|
.field public g:F
|
||||||
|
|
||||||
|
# X at ACTION_DOWN
|
||||||
|
.field public i:F
|
||||||
|
|
||||||
|
# True once horizontal movement wins (prevents later vertical start)
|
||||||
|
.field public j:Z
|
||||||
|
|
||||||
|
# Drag option enabled
|
||||||
|
.field public final k:Z
|
||||||
|
|
||||||
|
|
||||||
|
# direct methods
|
||||||
|
.method public constructor <init>(Lyl0/l;Landroidx/compose/material3/SheetState;Z)V
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||||
|
|
||||||
|
iput-object p1, p0, Lradiant/MiniPlayerGestures$Gesture;->a:Lyl0/l;
|
||||||
|
|
||||||
|
iput-object p2, p0, Lradiant/MiniPlayerGestures$Gesture;->f:Landroidx/compose/material3/SheetState;
|
||||||
|
|
||||||
|
iput-boolean p3, p0, Lradiant/MiniPlayerGestures$Gesture;->k:Z
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method private static dp(F)F
|
||||||
|
.locals 1
|
||||||
|
|
||||||
|
invoke-static {}, Landroid/content/res/Resources;->getSystem()Landroid/content/res/Resources;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Landroid/content/res/Resources;->getDisplayMetrics()Landroid/util/DisplayMetrics;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
iget v0, v0, Landroid/util/DisplayMetrics;->density:F
|
||||||
|
|
||||||
|
mul-float/2addr p0, v0
|
||||||
|
|
||||||
|
return p0
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method private dragSheet(FJ)V
|
||||||
|
.locals 1
|
||||||
|
|
||||||
|
iget-object v0, p0, Lradiant/MiniPlayerGestures$Gesture;->f:Landroidx/compose/material3/SheetState;
|
||||||
|
|
||||||
|
invoke-static {v0, p1, p2, p3}, Lradiant/MiniPlayerGestures;->updateDragTimed(Landroidx/compose/material3/SheetState;FJ)V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method private finishSheet(F)V
|
||||||
|
.locals 2
|
||||||
|
|
||||||
|
iget-object v0, p0, Lradiant/MiniPlayerGestures$Gesture;->f:Landroidx/compose/material3/SheetState;
|
||||||
|
|
||||||
|
iget-object v1, p0, Lradiant/MiniPlayerGestures$Gesture;->a:Lyl0/l;
|
||||||
|
|
||||||
|
invoke-static {v0, v1, p1}, Lradiant/MiniPlayerGestures;->finishDrag(Landroidx/compose/material3/SheetState;Lyl0/l;F)V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method private openPlayer()V
|
||||||
|
.locals 2
|
||||||
|
|
||||||
|
iget-object v0, p0, Lradiant/MiniPlayerGestures$Gesture;->a:Lyl0/l;
|
||||||
|
|
||||||
|
sget-object v1, Lcom/tidal/android/feature/appscaffold/ui/b$b;->a:Lcom/tidal/android/feature/appscaffold/ui/b$b;
|
||||||
|
|
||||||
|
invoke-interface {v0, v1}, Lyl0/l;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method private reset()V
|
||||||
|
.locals 1
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
iput-boolean v0, p0, Lradiant/MiniPlayerGestures$Gesture;->d:Z
|
||||||
|
|
||||||
|
iput-boolean v0, p0, Lradiant/MiniPlayerGestures$Gesture;->e:Z
|
||||||
|
|
||||||
|
iput-boolean v0, p0, Lradiant/MiniPlayerGestures$Gesture;->j:Z
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
iput v0, p0, Lradiant/MiniPlayerGestures$Gesture;->i:F
|
||||||
|
|
||||||
|
iput v0, p0, Lradiant/MiniPlayerGestures$Gesture;->g:F
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method private startSheet(FJ)V
|
||||||
|
.locals 2
|
||||||
|
|
||||||
|
# empty player guard
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerGestures;->hasMiniPlayerMedia()Z
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
if-nez v0, :start_guard
|
||||||
|
|
||||||
|
return-void
|
||||||
|
|
||||||
|
:start_guard
|
||||||
|
|
||||||
|
# avoid starting twice
|
||||||
|
iget-boolean v0, p0, Lradiant/MiniPlayerGestures$Gesture;->e:Z
|
||||||
|
|
||||||
|
if-eqz v0, :start
|
||||||
|
|
||||||
|
return-void
|
||||||
|
|
||||||
|
:start
|
||||||
|
const/4 v0, 0x1
|
||||||
|
|
||||||
|
iput-boolean v0, p0, Lradiant/MiniPlayerGestures$Gesture;->e:Z
|
||||||
|
|
||||||
|
iget-boolean v0, p0, Lradiant/MiniPlayerGestures$Gesture;->k:Z
|
||||||
|
|
||||||
|
if-eqz v0, :done
|
||||||
|
|
||||||
|
iget-object v1, p0, Lradiant/MiniPlayerGestures$Gesture;->f:Landroidx/compose/material3/SheetState;
|
||||||
|
|
||||||
|
iget v0, p0, Lradiant/MiniPlayerGestures$Gesture;->b:F
|
||||||
|
|
||||||
|
invoke-static {v1, p1, v0, p2, p3}, Lradiant/MiniPlayerGestures;->beginDrag(Landroidx/compose/material3/SheetState;FFJ)V
|
||||||
|
|
||||||
|
invoke-direct {p0}, Lradiant/MiniPlayerGestures$Gesture;->openPlayer()V
|
||||||
|
|
||||||
|
:done
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
# virtual methods
|
||||||
|
# Listens for MotionEvents
|
||||||
|
.method public final invoke(Landroid/view/MotionEvent;)Lkotlin/u;
|
||||||
|
.locals 11
|
||||||
|
|
||||||
|
invoke-virtual {p1}, Landroid/view/MotionEvent;->getActionMasked()I
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
if-eqz v0, :down
|
||||||
|
|
||||||
|
const/4 v1, 0x1
|
||||||
|
|
||||||
|
if-eq v0, v1, :up
|
||||||
|
|
||||||
|
const/4 v1, 0x2
|
||||||
|
|
||||||
|
if-eq v0, v1, :move
|
||||||
|
|
||||||
|
const/4 v1, 0x3
|
||||||
|
|
||||||
|
if-eq v0, v1, :cancel
|
||||||
|
|
||||||
|
goto :done
|
||||||
|
|
||||||
|
# ACTION_DOWN
|
||||||
|
:down
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerGestures;->resetGestureLock()V
|
||||||
|
|
||||||
|
invoke-static {p1}, Lradiant/MiniPlayerGestures;->beginMotion(Landroid/view/MotionEvent;)V
|
||||||
|
|
||||||
|
invoke-virtual {p1}, Landroid/view/MotionEvent;->getRawY()F
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
iput v0, p0, Lradiant/MiniPlayerGestures$Gesture;->b:F
|
||||||
|
|
||||||
|
iput v0, p0, Lradiant/MiniPlayerGestures$Gesture;->c:F
|
||||||
|
|
||||||
|
const/4 v0, 0x1
|
||||||
|
|
||||||
|
iput-boolean v0, p0, Lradiant/MiniPlayerGestures$Gesture;->d:Z
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
iput-boolean v0, p0, Lradiant/MiniPlayerGestures$Gesture;->e:Z
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
iput v0, p0, Lradiant/MiniPlayerGestures$Gesture;->g:F
|
||||||
|
|
||||||
|
invoke-virtual {p1}, Landroid/view/MotionEvent;->getRawX()F
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
iput v0, p0, Lradiant/MiniPlayerGestures$Gesture;->i:F
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
iput-boolean v0, p0, Lradiant/MiniPlayerGestures$Gesture;->j:Z
|
||||||
|
|
||||||
|
iget v0, p0, Lradiant/MiniPlayerGestures$Gesture;->b:F
|
||||||
|
|
||||||
|
invoke-static {v0}, Lradiant/MiniPlayerGestures;->beginSwipeGesture(F)V
|
||||||
|
|
||||||
|
:skip_feedback_down
|
||||||
|
|
||||||
|
goto :done
|
||||||
|
|
||||||
|
# ACTION_MOVE
|
||||||
|
:move
|
||||||
|
iget-boolean v0, p0, Lradiant/MiniPlayerGestures$Gesture;->d:Z
|
||||||
|
|
||||||
|
if-eqz v0, :done
|
||||||
|
|
||||||
|
invoke-static {p1}, Lradiant/MiniPlayerGestures;->trackMotion(Landroid/view/MotionEvent;)V
|
||||||
|
|
||||||
|
invoke-virtual {p1}, Landroid/view/MotionEvent;->getRawY()F
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
invoke-virtual {p1}, Landroid/view/MotionEvent;->getEventTime()J
|
||||||
|
|
||||||
|
move-result-wide v5
|
||||||
|
|
||||||
|
iget v1, p0, Lradiant/MiniPlayerGestures$Gesture;->c:F
|
||||||
|
|
||||||
|
sub-float v1, v0, v1
|
||||||
|
|
||||||
|
iput v1, p0, Lradiant/MiniPlayerGestures$Gesture;->g:F
|
||||||
|
|
||||||
|
iget v2, p0, Lradiant/MiniPlayerGestures$Gesture;->b:F
|
||||||
|
|
||||||
|
sub-float v2, v0, v2
|
||||||
|
|
||||||
|
iget-boolean v3, p0, Lradiant/MiniPlayerGestures$Gesture;->e:Z
|
||||||
|
|
||||||
|
if-eqz v3, :maybe_start
|
||||||
|
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerGestures;->suppressHorizontalGestures()V
|
||||||
|
|
||||||
|
iget-boolean v3, p0, Lradiant/MiniPlayerGestures$Gesture;->k:Z
|
||||||
|
|
||||||
|
if-eqz v3, :feedback_active
|
||||||
|
|
||||||
|
invoke-direct {p0, v2, v5, v6}, Lradiant/MiniPlayerGestures$Gesture;->dragSheet(FJ)V
|
||||||
|
|
||||||
|
:feedback_active
|
||||||
|
invoke-static {v2}, Lradiant/MiniPlayerGestures;->setSwipeFeedbackFromDrag(F)V
|
||||||
|
|
||||||
|
goto :store_last
|
||||||
|
|
||||||
|
:maybe_start
|
||||||
|
iget-boolean v3, p0, Lradiant/MiniPlayerGestures$Gesture;->j:Z
|
||||||
|
|
||||||
|
if-eqz v3, :axis_check
|
||||||
|
|
||||||
|
goto :store_last
|
||||||
|
|
||||||
|
:axis_check
|
||||||
|
invoke-virtual {p1}, Landroid/view/MotionEvent;->getRawX()F
|
||||||
|
|
||||||
|
move-result v7
|
||||||
|
|
||||||
|
iget v8, p0, Lradiant/MiniPlayerGestures$Gesture;->i:F
|
||||||
|
|
||||||
|
sub-float/2addr v7, v8
|
||||||
|
|
||||||
|
invoke-static {v7}, Ljava/lang/Math;->abs(F)F
|
||||||
|
|
||||||
|
move-result v7
|
||||||
|
|
||||||
|
invoke-static {v2}, Ljava/lang/Math;->abs(F)F
|
||||||
|
|
||||||
|
move-result v8
|
||||||
|
|
||||||
|
const/high16 v9, 0x40c00000 # 6.0f axis deadzone
|
||||||
|
|
||||||
|
invoke-static {v9}, Lradiant/MiniPlayerGestures$Gesture;->dp(F)F
|
||||||
|
|
||||||
|
move-result v9
|
||||||
|
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerGestures;->isHorizontalGestureLocked()Z
|
||||||
|
|
||||||
|
move-result v10
|
||||||
|
|
||||||
|
if-eqz v10, :horizontal_check
|
||||||
|
|
||||||
|
const/4 v10, 0x0
|
||||||
|
|
||||||
|
invoke-static {v10}, Lradiant/MiniPlayerGestures;->setSwipeFeedbackDirect(F)V
|
||||||
|
|
||||||
|
goto :store_last
|
||||||
|
|
||||||
|
:horizontal_check
|
||||||
|
|
||||||
|
cmpg-float v10, v7, v9
|
||||||
|
|
||||||
|
if-gez v10, :horizontal_dominance
|
||||||
|
|
||||||
|
goto :vertical_check
|
||||||
|
|
||||||
|
:horizontal_dominance
|
||||||
|
|
||||||
|
const v10, 0x3f8ccccd # 1.1f
|
||||||
|
|
||||||
|
mul-float/2addr v10, v8
|
||||||
|
|
||||||
|
cmpg-float v10, v7, v10
|
||||||
|
|
||||||
|
if-gez v10, :horizontal_wins
|
||||||
|
|
||||||
|
goto :vertical_check
|
||||||
|
|
||||||
|
:horizontal_wins
|
||||||
|
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerGestures;->lockHorizontalGesture()Z
|
||||||
|
|
||||||
|
move-result v10
|
||||||
|
|
||||||
|
if-eqz v10, :store_last
|
||||||
|
|
||||||
|
const/4 v10, 0x1
|
||||||
|
|
||||||
|
iput-boolean v10, p0, Lradiant/MiniPlayerGestures$Gesture;->j:Z
|
||||||
|
|
||||||
|
const/4 v10, 0x0
|
||||||
|
|
||||||
|
invoke-static {v10}, Lradiant/MiniPlayerGestures;->setSwipeFeedbackDirect(F)V
|
||||||
|
|
||||||
|
goto :store_last
|
||||||
|
|
||||||
|
# At least 16dp up to open the player
|
||||||
|
:vertical_check
|
||||||
|
neg-float v9, v9
|
||||||
|
|
||||||
|
cmpg-float v10, v2, v9
|
||||||
|
|
||||||
|
if-lez v10, :vertical_dominance
|
||||||
|
|
||||||
|
invoke-static {v2}, Lradiant/MiniPlayerGestures;->setSwipeFeedbackFromDrag(F)V
|
||||||
|
|
||||||
|
goto :store_last
|
||||||
|
|
||||||
|
:vertical_dominance
|
||||||
|
const v10, 0x3f8ccccd # 1.1f -> early vertical axis lock
|
||||||
|
|
||||||
|
mul-float/2addr v7, v10
|
||||||
|
|
||||||
|
cmpl-float v10, v8, v7
|
||||||
|
|
||||||
|
if-gtz v10, :begin
|
||||||
|
|
||||||
|
goto :store_last
|
||||||
|
|
||||||
|
:begin
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerGestures;->lockVerticalGesture()Z
|
||||||
|
|
||||||
|
move-result v10
|
||||||
|
|
||||||
|
if-eqz v10, :store_last
|
||||||
|
|
||||||
|
invoke-direct {p0, v2, v5, v6}, Lradiant/MiniPlayerGestures$Gesture;->startSheet(FJ)V
|
||||||
|
|
||||||
|
iget-boolean v10, p0, Lradiant/MiniPlayerGestures$Gesture;->e:Z
|
||||||
|
|
||||||
|
if-eqz v10, :store_last
|
||||||
|
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerGestures;->suppressHorizontalGestures()V
|
||||||
|
|
||||||
|
iget-boolean v10, p0, Lradiant/MiniPlayerGestures$Gesture;->k:Z
|
||||||
|
|
||||||
|
if-eqz v10, :feedback_begin
|
||||||
|
|
||||||
|
invoke-direct {p0, v2, v5, v6}, Lradiant/MiniPlayerGestures$Gesture;->dragSheet(FJ)V
|
||||||
|
|
||||||
|
:feedback_begin
|
||||||
|
invoke-static {v2}, Lradiant/MiniPlayerGestures;->setSwipeFeedbackFromDrag(F)V
|
||||||
|
|
||||||
|
|
||||||
|
:store_last
|
||||||
|
iput v0, p0, Lradiant/MiniPlayerGestures$Gesture;->c:F
|
||||||
|
|
||||||
|
goto :done
|
||||||
|
|
||||||
|
# ACTION_UP
|
||||||
|
:up
|
||||||
|
iget-boolean v0, p0, Lradiant/MiniPlayerGestures$Gesture;->d:Z
|
||||||
|
|
||||||
|
if-eqz v0, :done
|
||||||
|
|
||||||
|
iget-boolean v0, p0, Lradiant/MiniPlayerGestures$Gesture;->e:Z
|
||||||
|
|
||||||
|
if-eqz v0, :up_no_drag
|
||||||
|
|
||||||
|
iget-boolean v0, p0, Lradiant/MiniPlayerGestures$Gesture;->k:Z
|
||||||
|
|
||||||
|
if-eqz v0, :open_simple
|
||||||
|
|
||||||
|
invoke-static {p1}, Lradiant/MiniPlayerGestures;->trackMotion(Landroid/view/MotionEvent;)V
|
||||||
|
|
||||||
|
invoke-virtual {p1}, Landroid/view/MotionEvent;->getRawY()F
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
invoke-virtual {p1}, Landroid/view/MotionEvent;->getEventTime()J
|
||||||
|
|
||||||
|
move-result-wide v5
|
||||||
|
|
||||||
|
iget-object v1, p0, Lradiant/MiniPlayerGestures$Gesture;->f:Landroidx/compose/material3/SheetState;
|
||||||
|
|
||||||
|
invoke-static {v1, v0, v5, v6}, Lradiant/MiniPlayerGestures;->updateDragToYTimed(Landroidx/compose/material3/SheetState;FJ)V
|
||||||
|
|
||||||
|
invoke-static {v1}, Lradiant/MiniPlayerGestures;->releaseVelocity(Landroidx/compose/material3/SheetState;)F
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
invoke-direct {p0, v0}, Lradiant/MiniPlayerGestures$Gesture;->finishSheet(F)V
|
||||||
|
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerGestures;->animateSwipeFeedbackReset()V
|
||||||
|
|
||||||
|
goto :finish
|
||||||
|
|
||||||
|
:up_no_drag
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerGestures;->animateSwipeFeedbackReset()V
|
||||||
|
|
||||||
|
goto :finish
|
||||||
|
|
||||||
|
:open_simple
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerGestures;->suppressHorizontalGestures()V
|
||||||
|
|
||||||
|
invoke-virtual {p1}, Landroid/view/MotionEvent;->getRawY()F
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
iget-object v1, p0, Lradiant/MiniPlayerGestures$Gesture;->a:Lyl0/l;
|
||||||
|
|
||||||
|
invoke-static {v1, v0}, Lradiant/MiniPlayerGestures;->completeSimpleSwipe(Lyl0/l;F)Z
|
||||||
|
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerGestures;->animateSwipeFeedbackReset()V
|
||||||
|
|
||||||
|
|
||||||
|
:finish
|
||||||
|
invoke-direct {p0}, Lradiant/MiniPlayerGestures$Gesture;->reset()V
|
||||||
|
|
||||||
|
goto :done
|
||||||
|
|
||||||
|
# ACTION_CANCEL
|
||||||
|
:cancel
|
||||||
|
iget-boolean v0, p0, Lradiant/MiniPlayerGestures$Gesture;->e:Z
|
||||||
|
|
||||||
|
if-eqz v0, :cancel_no_active
|
||||||
|
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerGestures;->animateSwipeFeedbackReset()V
|
||||||
|
|
||||||
|
iget-object v0, p0, Lradiant/MiniPlayerGestures$Gesture;->f:Landroidx/compose/material3/SheetState;
|
||||||
|
|
||||||
|
iget-object v1, p0, Lradiant/MiniPlayerGestures$Gesture;->a:Lyl0/l;
|
||||||
|
|
||||||
|
invoke-static {v0, v1}, Lradiant/MiniPlayerGestures;->cancelDrag(Landroidx/compose/material3/SheetState;Lyl0/l;)V
|
||||||
|
|
||||||
|
goto :cancel_reset
|
||||||
|
|
||||||
|
:cancel_no_active
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerGestures;->animateSwipeFeedbackReset()V
|
||||||
|
|
||||||
|
:cancel_reset
|
||||||
|
invoke-direct {p0}, Lradiant/MiniPlayerGestures$Gesture;->reset()V
|
||||||
|
|
||||||
|
|
||||||
|
:done
|
||||||
|
sget-object p1, Lkotlin/u;->a:Lkotlin/u;
|
||||||
|
|
||||||
|
return-object p1
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public bridge synthetic invoke(Ljava/lang/Object;)Ljava/lang/Object;
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
check-cast p1, Landroid/view/MotionEvent;
|
||||||
|
|
||||||
|
invoke-virtual {p0, p1}, Lradiant/MiniPlayerGestures$Gesture;->invoke(Landroid/view/MotionEvent;)Lkotlin/u;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
|
||||||
|
return-object p1
|
||||||
|
.end method
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
.class public final Lradiant/MiniPlayerGestures$RootGesture;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
.implements Lyl0/l;
|
||||||
|
|
||||||
|
|
||||||
|
# annotations
|
||||||
|
.annotation system Ldalvik/annotation/EnclosingClass;
|
||||||
|
value = Lradiant/MiniPlayerGestures;
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
.annotation system Ldalvik/annotation/InnerClass;
|
||||||
|
accessFlags = 0x19
|
||||||
|
name = "RootGesture"
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
|
||||||
|
# instance fields
|
||||||
|
.field public final a:Lyl0/l;
|
||||||
|
|
||||||
|
.field public final b:Landroidx/compose/material3/SheetState;
|
||||||
|
|
||||||
|
|
||||||
|
# direct methods
|
||||||
|
.method public constructor <init>(Lyl0/l;Landroidx/compose/material3/SheetState;)V
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||||
|
|
||||||
|
iput-object p1, p0, Lradiant/MiniPlayerGestures$RootGesture;->a:Lyl0/l;
|
||||||
|
|
||||||
|
iput-object p2, p0, Lradiant/MiniPlayerGestures$RootGesture;->b:Landroidx/compose/material3/SheetState;
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
# virtual methods
|
||||||
|
.method public final invoke(Landroid/view/MotionEvent;)Lkotlin/u;
|
||||||
|
.locals 6
|
||||||
|
|
||||||
|
iget-object v0, p0, Lradiant/MiniPlayerGestures$RootGesture;->b:Landroidx/compose/material3/SheetState;
|
||||||
|
|
||||||
|
invoke-virtual {p1}, Landroid/view/MotionEvent;->getActionMasked()I
|
||||||
|
|
||||||
|
move-result v1
|
||||||
|
|
||||||
|
invoke-static {v0}, Lradiant/MiniPlayerGestures;->isDragging(Landroidx/compose/material3/SheetState;)Z
|
||||||
|
|
||||||
|
move-result v2
|
||||||
|
|
||||||
|
if-eqz v2, :maybe_reset_only
|
||||||
|
|
||||||
|
const/4 v2, 0x1
|
||||||
|
|
||||||
|
if-eq v1, v2, :up
|
||||||
|
|
||||||
|
const/4 v2, 0x2
|
||||||
|
|
||||||
|
if-eq v1, v2, :move
|
||||||
|
|
||||||
|
const/4 v2, 0x3
|
||||||
|
|
||||||
|
if-eq v1, v2, :cancel
|
||||||
|
|
||||||
|
goto :done
|
||||||
|
|
||||||
|
:maybe_reset_only
|
||||||
|
const/4 v2, 0x1
|
||||||
|
|
||||||
|
if-eq v1, v2, :simple_up
|
||||||
|
|
||||||
|
const/4 v2, 0x3
|
||||||
|
|
||||||
|
if-eq v1, v2, :reset_only
|
||||||
|
|
||||||
|
const/4 v2, 0x2
|
||||||
|
|
||||||
|
if-eq v1, v2, :simple_move
|
||||||
|
|
||||||
|
goto :done
|
||||||
|
|
||||||
|
:reset_only
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerGestures;->resetGestureLock()V
|
||||||
|
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerGestures;->animateSwipeFeedbackReset()V
|
||||||
|
|
||||||
|
goto :done
|
||||||
|
|
||||||
|
:simple_move
|
||||||
|
invoke-virtual {p1}, Landroid/view/MotionEvent;->getRawY()F
|
||||||
|
|
||||||
|
move-result v1
|
||||||
|
|
||||||
|
invoke-static {v1}, Lradiant/MiniPlayerGestures;->updateSimpleSwipe(F)Z
|
||||||
|
|
||||||
|
goto :done
|
||||||
|
|
||||||
|
:simple_up
|
||||||
|
invoke-virtual {p1}, Landroid/view/MotionEvent;->getRawY()F
|
||||||
|
|
||||||
|
move-result v1
|
||||||
|
|
||||||
|
iget-object v2, p0, Lradiant/MiniPlayerGestures$RootGesture;->a:Lyl0/l;
|
||||||
|
|
||||||
|
invoke-static {v2, v1}, Lradiant/MiniPlayerGestures;->completeSimpleSwipe(Lyl0/l;F)Z
|
||||||
|
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerGestures;->animateSwipeFeedbackReset()V
|
||||||
|
|
||||||
|
goto :done
|
||||||
|
|
||||||
|
|
||||||
|
# ACTION_MOVE
|
||||||
|
:move
|
||||||
|
invoke-static {p1}, Lradiant/MiniPlayerGestures;->trackMotion(Landroid/view/MotionEvent;)V
|
||||||
|
|
||||||
|
invoke-virtual {p1}, Landroid/view/MotionEvent;->getRawY()F
|
||||||
|
|
||||||
|
move-result v2
|
||||||
|
|
||||||
|
invoke-virtual {p1}, Landroid/view/MotionEvent;->getEventTime()J
|
||||||
|
|
||||||
|
move-result-wide v4
|
||||||
|
|
||||||
|
invoke-static {v0, v2, v4, v5}, Lradiant/MiniPlayerGestures;->updateDragToYTimed(Landroidx/compose/material3/SheetState;FJ)V
|
||||||
|
|
||||||
|
goto :done
|
||||||
|
|
||||||
|
# ACTION_UP
|
||||||
|
:up
|
||||||
|
invoke-static {p1}, Lradiant/MiniPlayerGestures;->trackMotion(Landroid/view/MotionEvent;)V
|
||||||
|
|
||||||
|
invoke-virtual {p1}, Landroid/view/MotionEvent;->getRawY()F
|
||||||
|
|
||||||
|
move-result v1
|
||||||
|
|
||||||
|
invoke-virtual {p1}, Landroid/view/MotionEvent;->getEventTime()J
|
||||||
|
|
||||||
|
move-result-wide v4
|
||||||
|
|
||||||
|
invoke-static {v0, v1, v4, v5}, Lradiant/MiniPlayerGestures;->updateDragToYTimed(Landroidx/compose/material3/SheetState;FJ)V
|
||||||
|
|
||||||
|
invoke-static {v0}, Lradiant/MiniPlayerGestures;->releaseVelocity(Landroidx/compose/material3/SheetState;)F
|
||||||
|
|
||||||
|
move-result v1
|
||||||
|
|
||||||
|
iget-object v2, p0, Lradiant/MiniPlayerGestures$RootGesture;->a:Lyl0/l;
|
||||||
|
|
||||||
|
invoke-static {v0, v2, v1}, Lradiant/MiniPlayerGestures;->finishDrag(Landroidx/compose/material3/SheetState;Lyl0/l;F)V
|
||||||
|
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerGestures;->animateSwipeFeedbackReset()V
|
||||||
|
|
||||||
|
goto :done
|
||||||
|
|
||||||
|
# ACTION_CANCEL
|
||||||
|
:cancel
|
||||||
|
iget-object v2, p0, Lradiant/MiniPlayerGestures$RootGesture;->a:Lyl0/l;
|
||||||
|
|
||||||
|
invoke-static {v0, v2}, Lradiant/MiniPlayerGestures;->cancelDrag(Landroidx/compose/material3/SheetState;Lyl0/l;)V
|
||||||
|
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerGestures;->animateSwipeFeedbackReset()V
|
||||||
|
|
||||||
|
:done
|
||||||
|
sget-object p1, Lkotlin/u;->a:Lkotlin/u;
|
||||||
|
|
||||||
|
return-object p1
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public bridge synthetic invoke(Ljava/lang/Object;)Ljava/lang/Object;
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
check-cast p1, Landroid/view/MotionEvent;
|
||||||
|
|
||||||
|
invoke-virtual {p0, p1}, Lradiant/MiniPlayerGestures$RootGesture;->invoke(Landroid/view/MotionEvent;)Lkotlin/u;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
|
||||||
|
return-object p1
|
||||||
|
.end method
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,150 @@
|
|||||||
|
.class public final Lradiant/MiniPlayerTrackGestures$Gesture;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
.implements Lyl0/l;
|
||||||
|
|
||||||
|
|
||||||
|
# annotations
|
||||||
|
.annotation system Ldalvik/annotation/EnclosingClass;
|
||||||
|
value = Lradiant/MiniPlayerTrackGestures;
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
.annotation system Ldalvik/annotation/InnerClass;
|
||||||
|
accessFlags = 0x19
|
||||||
|
name = "Gesture"
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
|
||||||
|
# instance fields
|
||||||
|
.field public final a:Lyl0/l;
|
||||||
|
|
||||||
|
|
||||||
|
# direct methods
|
||||||
|
.method public constructor <init>(Lyl0/l;)V
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||||
|
|
||||||
|
iput-object p1, p0, Lradiant/MiniPlayerTrackGestures$Gesture;->a:Lyl0/l;
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method private nextTrack()V
|
||||||
|
.locals 2
|
||||||
|
|
||||||
|
iget-object v0, p0, Lradiant/MiniPlayerTrackGestures$Gesture;->a:Lyl0/l;
|
||||||
|
|
||||||
|
sget-object v1, Lcom/tidal/android/feature/appscaffold/ui/q$a;->a:Lcom/tidal/android/feature/appscaffold/ui/q$a; # Tidal's built-in next track mini-player event
|
||||||
|
|
||||||
|
invoke-interface {v0, v1}, Lyl0/l;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method private previousTrack()V
|
||||||
|
.locals 2
|
||||||
|
|
||||||
|
iget-object v0, p0, Lradiant/MiniPlayerTrackGestures$Gesture;->a:Lyl0/l;
|
||||||
|
|
||||||
|
sget-object v1, Lcom/tidal/android/feature/appscaffold/ui/q$c;->a:Lcom/tidal/android/feature/appscaffold/ui/q$c; # Our synthetic previous track event
|
||||||
|
|
||||||
|
invoke-interface {v0, v1}, Lyl0/l;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
# virtual methods
|
||||||
|
.method public final invoke(Landroid/view/MotionEvent;)Lkotlin/u;
|
||||||
|
.locals 3
|
||||||
|
|
||||||
|
invoke-virtual {p1}, Landroid/view/MotionEvent;->getActionMasked()I
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
if-eqz v0, :down
|
||||||
|
|
||||||
|
const/4 v1, 0x1
|
||||||
|
|
||||||
|
if-eq v0, v1, :up
|
||||||
|
|
||||||
|
const/4 v1, 0x2
|
||||||
|
|
||||||
|
if-eq v0, v1, :move
|
||||||
|
|
||||||
|
const/4 v1, 0x3
|
||||||
|
|
||||||
|
if-eq v0, v1, :cancel
|
||||||
|
|
||||||
|
goto :done
|
||||||
|
|
||||||
|
# ACTION_DOWN
|
||||||
|
:down
|
||||||
|
invoke-virtual {p1}, Landroid/view/MotionEvent;->getRawX()F
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
invoke-virtual {p1}, Landroid/view/MotionEvent;->getRawY()F
|
||||||
|
|
||||||
|
move-result v1
|
||||||
|
invoke-static {v0, v1}, Lradiant/MiniPlayerTrackGestures;->beginDrag(FF)V
|
||||||
|
goto :done
|
||||||
|
|
||||||
|
# ACTION_MOVE
|
||||||
|
:move
|
||||||
|
invoke-virtual {p1}, Landroid/view/MotionEvent;->getRawX()F
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
invoke-virtual {p1}, Landroid/view/MotionEvent;->getRawY()F
|
||||||
|
|
||||||
|
move-result v1
|
||||||
|
invoke-static {v0, v1}, Lradiant/MiniPlayerTrackGestures;->moveDrag(FF)V
|
||||||
|
goto :done
|
||||||
|
|
||||||
|
# ACTION_UP
|
||||||
|
:up
|
||||||
|
invoke-virtual {p1}, Landroid/view/MotionEvent;->getRawX()F
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
invoke-virtual {p1}, Landroid/view/MotionEvent;->getRawY()F
|
||||||
|
|
||||||
|
move-result v1
|
||||||
|
invoke-static {v0, v1}, Lradiant/MiniPlayerTrackGestures;->finishDrag(FF)I
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
if-eqz v0, :done
|
||||||
|
|
||||||
|
# Negative result = swipe left -> next. Positive result = swipe right -> previous
|
||||||
|
if-gez v0, :previous
|
||||||
|
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerTrackGestures;->suppressTapOpen()V
|
||||||
|
invoke-direct {p0}, Lradiant/MiniPlayerTrackGestures$Gesture;->nextTrack()V
|
||||||
|
goto :done
|
||||||
|
|
||||||
|
:previous
|
||||||
|
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerTrackGestures;->suppressTapOpen()V
|
||||||
|
invoke-direct {p0}, Lradiant/MiniPlayerTrackGestures$Gesture;->previousTrack()V
|
||||||
|
goto :done
|
||||||
|
|
||||||
|
# ACTION_CANCEL
|
||||||
|
:cancel
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerTrackGestures;->cancelDrag()V
|
||||||
|
|
||||||
|
:done
|
||||||
|
sget-object p1, Lkotlin/u;->a:Lkotlin/u;
|
||||||
|
|
||||||
|
return-object p1
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public bridge synthetic invoke(Ljava/lang/Object;)Ljava/lang/Object;
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
check-cast p1, Landroid/view/MotionEvent;
|
||||||
|
|
||||||
|
invoke-virtual {p0, p1}, Lradiant/MiniPlayerTrackGestures$Gesture;->invoke(Landroid/view/MotionEvent;)Lkotlin/u;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
|
||||||
|
return-object p1
|
||||||
|
.end method
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
.class public final Lradiant/MiniPlayerTrackGestures$OffsetLayer;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
.implements Lyl0/l;
|
||||||
|
|
||||||
|
|
||||||
|
# annotations
|
||||||
|
.annotation system Ldalvik/annotation/EnclosingClass;
|
||||||
|
value = Lradiant/MiniPlayerTrackGestures;
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
.annotation system Ldalvik/annotation/InnerClass;
|
||||||
|
accessFlags = 0x19
|
||||||
|
name = "OffsetLayer"
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
|
||||||
|
# static fields
|
||||||
|
.field public static final INSTANCE:Lradiant/MiniPlayerTrackGestures$OffsetLayer;
|
||||||
|
|
||||||
|
|
||||||
|
# direct methods
|
||||||
|
.method static constructor <clinit>()V
|
||||||
|
.locals 1
|
||||||
|
|
||||||
|
new-instance v0, Lradiant/MiniPlayerTrackGestures$OffsetLayer;
|
||||||
|
|
||||||
|
invoke-direct {v0}, Lradiant/MiniPlayerTrackGestures$OffsetLayer;-><init>()V
|
||||||
|
|
||||||
|
sput-object v0, Lradiant/MiniPlayerTrackGestures$OffsetLayer;->INSTANCE:Lradiant/MiniPlayerTrackGestures$OffsetLayer;
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method private constructor <init>()V
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
# virtual methods
|
||||||
|
.method public final invoke(Landroidx/compose/ui/graphics/GraphicsLayerScope;)Lkotlin/u;
|
||||||
|
.locals 1
|
||||||
|
|
||||||
|
sget-object v0, Lradiant/MiniPlayerTrackGestures;->b:Landroidx/compose/runtime/MutableFloatState;
|
||||||
|
|
||||||
|
invoke-interface {v0}, Landroidx/compose/runtime/MutableFloatState;->getFloatValue()F
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
invoke-interface {p1, v0}, Landroidx/compose/ui/graphics/GraphicsLayerScope;->setTranslationX(F)V
|
||||||
|
|
||||||
|
sget-object p1, Lkotlin/u;->a:Lkotlin/u;
|
||||||
|
|
||||||
|
return-object p1
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public bridge synthetic invoke(Ljava/lang/Object;)Ljava/lang/Object;
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
check-cast p1, Landroidx/compose/ui/graphics/GraphicsLayerScope;
|
||||||
|
|
||||||
|
invoke-virtual {p0, p1}, Lradiant/MiniPlayerTrackGestures$OffsetLayer;->invoke(Landroidx/compose/ui/graphics/GraphicsLayerScope;)Lkotlin/u;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
|
||||||
|
return-object p1
|
||||||
|
.end method
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
.class public final Lradiant/MiniPlayerTrackGestures$ResetAnimator;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
.implements Landroid/animation/ValueAnimator$AnimatorUpdateListener;
|
||||||
|
|
||||||
|
|
||||||
|
# annotations
|
||||||
|
.annotation system Ldalvik/annotation/EnclosingClass;
|
||||||
|
value = Lradiant/MiniPlayerTrackGestures;
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
.annotation system Ldalvik/annotation/InnerClass;
|
||||||
|
accessFlags = 0x19
|
||||||
|
name = "ResetAnimator"
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
|
||||||
|
# direct methods
|
||||||
|
.method public constructor <init>()V
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
# virtual methods
|
||||||
|
.method public final onAnimationUpdate(Landroid/animation/ValueAnimator;)V
|
||||||
|
.locals 3
|
||||||
|
|
||||||
|
move-object v0, p1
|
||||||
|
|
||||||
|
invoke-virtual {p1}, Landroid/animation/ValueAnimator;->getAnimatedValue()Ljava/lang/Object;
|
||||||
|
|
||||||
|
move-result-object v1
|
||||||
|
|
||||||
|
check-cast v1, Ljava/lang/Float;
|
||||||
|
|
||||||
|
invoke-virtual {v1}, Ljava/lang/Float;->floatValue()F
|
||||||
|
|
||||||
|
move-result v1
|
||||||
|
|
||||||
|
invoke-static {v1}, Lradiant/MiniPlayerTrackGestures;->setDragOffsetDirect(F)V
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Landroid/animation/ValueAnimator;->getAnimatedFraction()F
|
||||||
|
|
||||||
|
move-result v1
|
||||||
|
|
||||||
|
const/high16 v2, 0x3f800000 # 1.0f
|
||||||
|
|
||||||
|
cmpg-float v1, v1, v2
|
||||||
|
|
||||||
|
if-gez v1, :done
|
||||||
|
|
||||||
|
invoke-static {v0}, Lradiant/MiniPlayerTrackGestures;->clearResetAnimator(Landroid/animation/ValueAnimator;)V
|
||||||
|
|
||||||
|
:done
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
.class public final Lradiant/MiniPlayerTrackGestures$TextDraw;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
.implements Lyl0/l;
|
||||||
|
|
||||||
|
|
||||||
|
# annotations
|
||||||
|
.annotation system Ldalvik/annotation/EnclosingClass;
|
||||||
|
value = Lradiant/MiniPlayerTrackGestures;
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
.annotation system Ldalvik/annotation/InnerClass;
|
||||||
|
accessFlags = 0x19
|
||||||
|
name = "TextDraw"
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
|
||||||
|
# static fields
|
||||||
|
.field public static final INSTANCE:Lradiant/MiniPlayerTrackGestures$TextDraw;
|
||||||
|
|
||||||
|
|
||||||
|
# direct methods
|
||||||
|
.method static constructor <clinit>()V
|
||||||
|
.locals 1
|
||||||
|
|
||||||
|
new-instance v0, Lradiant/MiniPlayerTrackGestures$TextDraw;
|
||||||
|
|
||||||
|
invoke-direct {v0}, Lradiant/MiniPlayerTrackGestures$TextDraw;-><init>()V
|
||||||
|
|
||||||
|
sput-object v0, Lradiant/MiniPlayerTrackGestures$TextDraw;->INSTANCE:Lradiant/MiniPlayerTrackGestures$TextDraw;
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method private constructor <init>()V
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
# virtual methods
|
||||||
|
.method public final invoke(Landroidx/compose/ui/graphics/drawscope/ContentDrawScope;)Lkotlin/u;
|
||||||
|
.locals 12
|
||||||
|
|
||||||
|
sget-object v0, Lradiant/MiniPlayerTrackGestures;->b:Landroidx/compose/runtime/MutableFloatState;
|
||||||
|
|
||||||
|
invoke-interface {v0}, Landroidx/compose/runtime/MutableFloatState;->getFloatValue()F
|
||||||
|
|
||||||
|
move-result v10
|
||||||
|
|
||||||
|
invoke-interface {p1}, Landroidx/compose/ui/graphics/drawscope/DrawScope;->getDrawContext()Landroidx/compose/ui/graphics/drawscope/DrawContext;
|
||||||
|
|
||||||
|
move-result-object v7
|
||||||
|
|
||||||
|
invoke-interface {v7}, Landroidx/compose/ui/graphics/drawscope/DrawContext;->getSize-NH-jbRc()J
|
||||||
|
|
||||||
|
move-result-wide v8
|
||||||
|
|
||||||
|
const/16 v0, 0x20
|
||||||
|
|
||||||
|
shr-long v1, v8, v0
|
||||||
|
|
||||||
|
long-to-int v1, v1
|
||||||
|
|
||||||
|
invoke-static {v1}, Ljava/lang/Float;->intBitsToFloat(I)F
|
||||||
|
|
||||||
|
move-result v4
|
||||||
|
|
||||||
|
invoke-interface {v7}, Landroidx/compose/ui/graphics/drawscope/DrawContext;->getCanvas()Landroidx/compose/ui/graphics/Canvas;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
invoke-interface {v0}, Landroidx/compose/ui/graphics/Canvas;->save()V
|
||||||
|
|
||||||
|
:try_start_0
|
||||||
|
sget-object v0, Landroidx/compose/ui/graphics/ClipOp;->Companion:Landroidx/compose/ui/graphics/ClipOp$Companion;
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Landroidx/compose/ui/graphics/ClipOp$Companion;->getIntersect-rtfAjoo()I
|
||||||
|
|
||||||
|
move-result v6
|
||||||
|
|
||||||
|
invoke-interface {v7}, Landroidx/compose/ui/graphics/drawscope/DrawContext;->getTransform()Landroidx/compose/ui/graphics/drawscope/DrawTransform;
|
||||||
|
|
||||||
|
move-result-object v1
|
||||||
|
|
||||||
|
const v2, -0x800001 # -Float.MAX_VALUE -> unbounded left
|
||||||
|
|
||||||
|
const v3, -0x800001 # -Float.MAX_VALUE -> unbounded top
|
||||||
|
|
||||||
|
const v5, 0x7f7fffff # Float.MAX_VALUE -> unbounded bottom
|
||||||
|
|
||||||
|
invoke-interface/range {v1 .. v6}, Landroidx/compose/ui/graphics/drawscope/DrawTransform;->clipRect-N_I0leg(FFFFI)V
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
invoke-interface {v1, v10, v0}, Landroidx/compose/ui/graphics/drawscope/DrawTransform;->translate(FF)V
|
||||||
|
|
||||||
|
invoke-interface {p1}, Landroidx/compose/ui/graphics/drawscope/ContentDrawScope;->drawContent()V
|
||||||
|
:try_end_0
|
||||||
|
.catchall {:try_start_0 .. :try_end_0} :catchall_0
|
||||||
|
|
||||||
|
invoke-static {v7, v8, v9}, Landroidx/compose/animation/i;->b(Landroidx/compose/ui/graphics/drawscope/DrawContext;J)V
|
||||||
|
|
||||||
|
sget-object p1, Lkotlin/u;->a:Lkotlin/u;
|
||||||
|
|
||||||
|
return-object p1
|
||||||
|
|
||||||
|
:catchall_0
|
||||||
|
move-exception v0
|
||||||
|
|
||||||
|
invoke-static {v7, v8, v9}, Landroidx/compose/animation/i;->b(Landroidx/compose/ui/graphics/drawscope/DrawContext;J)V
|
||||||
|
|
||||||
|
throw v0
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public bridge synthetic invoke(Ljava/lang/Object;)Ljava/lang/Object;
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
check-cast p1, Landroidx/compose/ui/graphics/drawscope/ContentDrawScope;
|
||||||
|
|
||||||
|
invoke-virtual {p0, p1}, Lradiant/MiniPlayerTrackGestures$TextDraw;->invoke(Landroidx/compose/ui/graphics/drawscope/ContentDrawScope;)Lkotlin/u;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
|
||||||
|
return-object p1
|
||||||
|
.end method
|
||||||
@@ -0,0 +1,605 @@
|
|||||||
|
.class public final Lradiant/MiniPlayerTrackGestures;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
|
||||||
|
|
||||||
|
# static fields
|
||||||
|
.field private static a:J
|
||||||
|
|
||||||
|
.field public static b:Landroidx/compose/runtime/MutableFloatState;
|
||||||
|
|
||||||
|
.field private static c:Landroid/animation/ValueAnimator;
|
||||||
|
|
||||||
|
.field private static d:Ljava/lang/Object;
|
||||||
|
|
||||||
|
# X at ACTION_DOWN
|
||||||
|
.field private static e:F
|
||||||
|
|
||||||
|
# Y at ACTION_DOWN
|
||||||
|
.field private static f:F
|
||||||
|
|
||||||
|
.field private static g:Z
|
||||||
|
|
||||||
|
.field private static h:Z
|
||||||
|
|
||||||
|
|
||||||
|
# direct methods
|
||||||
|
.method static constructor <clinit>()V
|
||||||
|
.locals 1
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
invoke-static {v0}, Landroidx/compose/runtime/PrimitiveSnapshotStateKt;->mutableFloatStateOf(F)Landroidx/compose/runtime/MutableFloatState;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
sput-object v0, Lradiant/MiniPlayerTrackGestures;->b:Landroidx/compose/runtime/MutableFloatState;
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method private constructor <init>()V
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public static animateReset()V
|
||||||
|
.locals 7
|
||||||
|
|
||||||
|
sget-object v0, Lradiant/MiniPlayerTrackGestures;->c:Landroid/animation/ValueAnimator;
|
||||||
|
|
||||||
|
if-eqz v0, :read_offset
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Landroid/animation/ValueAnimator;->cancel()V
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
sput-object v0, Lradiant/MiniPlayerTrackGestures;->c:Landroid/animation/ValueAnimator;
|
||||||
|
|
||||||
|
:read_offset
|
||||||
|
sget-object v0, Lradiant/MiniPlayerTrackGestures;->b:Landroidx/compose/runtime/MutableFloatState;
|
||||||
|
|
||||||
|
invoke-interface {v0}, Landroidx/compose/runtime/MutableFloatState;->getFloatValue()F
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
invoke-static {v0}, Ljava/lang/Math;->abs(F)F
|
||||||
|
|
||||||
|
move-result v1
|
||||||
|
|
||||||
|
const/4 v3, 0x0
|
||||||
|
|
||||||
|
const/high16 v2, 0x3f000000 # 0.5f
|
||||||
|
|
||||||
|
cmpg-float v1, v1, v2
|
||||||
|
|
||||||
|
if-lez v1, :snap_zero
|
||||||
|
|
||||||
|
const/4 v1, 0x2
|
||||||
|
|
||||||
|
new-array v1, v1, [F
|
||||||
|
|
||||||
|
const/4 v2, 0x0
|
||||||
|
|
||||||
|
aput v0, v1, v2
|
||||||
|
|
||||||
|
const/4 v0, 0x1
|
||||||
|
|
||||||
|
aput v3, v1, v0
|
||||||
|
|
||||||
|
invoke-static {v1}, Landroid/animation/ValueAnimator;->ofFloat([F)Landroid/animation/ValueAnimator;
|
||||||
|
|
||||||
|
move-result-object v1
|
||||||
|
|
||||||
|
const-wide/16 v4, 0xb4 # 180ms
|
||||||
|
|
||||||
|
invoke-virtual {v1, v4, v5}, Landroid/animation/ValueAnimator;->setDuration(J)Landroid/animation/ValueAnimator;
|
||||||
|
|
||||||
|
new-instance v0, Landroid/view/animation/DecelerateInterpolator;
|
||||||
|
|
||||||
|
const/high16 v4, 0x3fc00000 # 1.5f
|
||||||
|
|
||||||
|
invoke-direct {v0, v4}, Landroid/view/animation/DecelerateInterpolator;-><init>(F)V
|
||||||
|
|
||||||
|
invoke-virtual {v1, v0}, Landroid/animation/ValueAnimator;->setInterpolator(Landroid/animation/TimeInterpolator;)V
|
||||||
|
|
||||||
|
new-instance v0, Lradiant/MiniPlayerTrackGestures$ResetAnimator;
|
||||||
|
|
||||||
|
invoke-direct {v0}, Lradiant/MiniPlayerTrackGestures$ResetAnimator;-><init>()V
|
||||||
|
|
||||||
|
invoke-virtual {v1, v0}, Landroid/animation/ValueAnimator;->addUpdateListener(Landroid/animation/ValueAnimator$AnimatorUpdateListener;)V
|
||||||
|
|
||||||
|
sput-object v1, Lradiant/MiniPlayerTrackGestures;->c:Landroid/animation/ValueAnimator;
|
||||||
|
|
||||||
|
invoke-virtual {v1}, Landroid/animation/ValueAnimator;->start()V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
|
||||||
|
:snap_zero
|
||||||
|
invoke-static {v3}, Lradiant/MiniPlayerTrackGestures;->setDragOffsetDirect(F)V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public static beginDrag(FF)V
|
||||||
|
.locals 2
|
||||||
|
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerGestures;->resetGestureLock()V
|
||||||
|
|
||||||
|
sput p0, Lradiant/MiniPlayerTrackGestures;->e:F
|
||||||
|
|
||||||
|
sput p1, Lradiant/MiniPlayerTrackGestures;->f:F
|
||||||
|
|
||||||
|
const/4 v0, 0x1
|
||||||
|
|
||||||
|
sput-boolean v0, Lradiant/MiniPlayerTrackGestures;->g:Z
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
sput-boolean v0, Lradiant/MiniPlayerTrackGestures;->h:Z
|
||||||
|
|
||||||
|
sget-object v0, Lradiant/MiniPlayerTrackGestures;->c:Landroid/animation/ValueAnimator;
|
||||||
|
|
||||||
|
if-eqz v0, :zero
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Landroid/animation/ValueAnimator;->cancel()V
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
sput-object v0, Lradiant/MiniPlayerTrackGestures;->c:Landroid/animation/ValueAnimator;
|
||||||
|
|
||||||
|
:zero
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
invoke-static {v0}, Lradiant/MiniPlayerTrackGestures;->setDragOffsetDirect(F)V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public static cancelDrag()V
|
||||||
|
.locals 1
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
sput-boolean v0, Lradiant/MiniPlayerTrackGestures;->g:Z
|
||||||
|
|
||||||
|
sput-boolean v0, Lradiant/MiniPlayerTrackGestures;->h:Z
|
||||||
|
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerTrackGestures;->animateReset()V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public static clearResetAnimator(Landroid/animation/ValueAnimator;)V
|
||||||
|
.locals 1
|
||||||
|
|
||||||
|
sget-object v0, Lradiant/MiniPlayerTrackGestures;->c:Landroid/animation/ValueAnimator;
|
||||||
|
|
||||||
|
if-ne v0, p0, :done
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
sput-object v0, Lradiant/MiniPlayerTrackGestures;->c:Landroid/animation/ValueAnimator;
|
||||||
|
|
||||||
|
:done
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public static finishDrag(FF)I
|
||||||
|
.locals 5
|
||||||
|
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerGestures;->isAnyDragging()Z
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
if-eqz v0, :check_active
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
sput-boolean v0, Lradiant/MiniPlayerTrackGestures;->g:Z
|
||||||
|
|
||||||
|
sput-boolean v0, Lradiant/MiniPlayerTrackGestures;->h:Z
|
||||||
|
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerTrackGestures;->animateReset()V
|
||||||
|
|
||||||
|
return v0
|
||||||
|
|
||||||
|
:check_active
|
||||||
|
|
||||||
|
sget-boolean v0, Lradiant/MiniPlayerTrackGestures;->g:Z
|
||||||
|
|
||||||
|
if-nez v0, :active
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
return v0
|
||||||
|
|
||||||
|
:active
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
sput-boolean v0, Lradiant/MiniPlayerTrackGestures;->g:Z
|
||||||
|
|
||||||
|
sget-boolean v1, Lradiant/MiniPlayerTrackGestures;->h:Z
|
||||||
|
|
||||||
|
sput-boolean v0, Lradiant/MiniPlayerTrackGestures;->h:Z
|
||||||
|
|
||||||
|
sget v2, Lradiant/MiniPlayerTrackGestures;->e:F
|
||||||
|
|
||||||
|
sub-float/2addr p0, v2
|
||||||
|
|
||||||
|
sget v2, Lradiant/MiniPlayerTrackGestures;->f:F
|
||||||
|
|
||||||
|
sub-float/2addr p1, v2
|
||||||
|
|
||||||
|
if-eqz v1, :reset
|
||||||
|
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerTrackGestures;->suppressTapOpen()V
|
||||||
|
|
||||||
|
:reset
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerTrackGestures;->animateReset()V
|
||||||
|
|
||||||
|
invoke-static {p0}, Ljava/lang/Math;->abs(F)F
|
||||||
|
|
||||||
|
move-result v1
|
||||||
|
|
||||||
|
const/high16 v2, 0x42400000 # 48.0f -> at least 48dp horizontal swipe
|
||||||
|
|
||||||
|
invoke-static {v2}, Lradiant/MiniPlayerTrackGestures;->dp(F)F
|
||||||
|
|
||||||
|
move-result v2
|
||||||
|
|
||||||
|
cmpg-float v3, v1, v2
|
||||||
|
|
||||||
|
if-gez v3, :check_axis
|
||||||
|
|
||||||
|
return v0
|
||||||
|
|
||||||
|
:check_axis
|
||||||
|
invoke-static {p1}, Ljava/lang/Math;->abs(F)F
|
||||||
|
|
||||||
|
move-result p1
|
||||||
|
|
||||||
|
const/high16 v2, 0x3fc00000 # 1.5f -> horizontal distance must dominate vertical mov. by 1.5x
|
||||||
|
|
||||||
|
mul-float/2addr p1, v2
|
||||||
|
|
||||||
|
cmpg-float v1, v1, p1
|
||||||
|
|
||||||
|
if-lez v1, :no_swipe
|
||||||
|
|
||||||
|
const/4 p1, 0x0
|
||||||
|
|
||||||
|
cmpg-float p0, p0, p1
|
||||||
|
|
||||||
|
if-ltz p0, :next
|
||||||
|
|
||||||
|
const/4 v0, 0x1
|
||||||
|
|
||||||
|
return v0
|
||||||
|
|
||||||
|
:next
|
||||||
|
const/4 v0, -0x1
|
||||||
|
|
||||||
|
return v0
|
||||||
|
|
||||||
|
:no_swipe
|
||||||
|
return v0
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public static moveDrag(FF)V
|
||||||
|
.locals 6
|
||||||
|
|
||||||
|
sget-boolean v0, Lradiant/MiniPlayerTrackGestures;->g:Z
|
||||||
|
|
||||||
|
if-eqz v0, :done
|
||||||
|
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerGestures;->isAnyDragging()Z
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
if-eqz v0, :move_continue
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
sput-boolean v0, Lradiant/MiniPlayerTrackGestures;->g:Z
|
||||||
|
|
||||||
|
sput-boolean v0, Lradiant/MiniPlayerTrackGestures;->h:Z
|
||||||
|
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerTrackGestures;->animateReset()V
|
||||||
|
|
||||||
|
goto :done
|
||||||
|
|
||||||
|
:move_continue
|
||||||
|
sget v0, Lradiant/MiniPlayerTrackGestures;->e:F
|
||||||
|
|
||||||
|
sub-float/2addr p0, v0
|
||||||
|
|
||||||
|
sget v0, Lradiant/MiniPlayerTrackGestures;->f:F
|
||||||
|
|
||||||
|
sub-float/2addr p1, v0
|
||||||
|
|
||||||
|
invoke-static {p0}, Ljava/lang/Math;->abs(F)F
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
invoke-static {p1}, Ljava/lang/Math;->abs(F)F
|
||||||
|
|
||||||
|
move-result v1
|
||||||
|
|
||||||
|
sget-boolean v2, Lradiant/MiniPlayerTrackGestures;->h:Z
|
||||||
|
|
||||||
|
if-nez v2, :publish
|
||||||
|
|
||||||
|
const/high16 v2, 0x40c00000 # 6.0f dp dead zone
|
||||||
|
|
||||||
|
invoke-static {v2}, Lradiant/MiniPlayerTrackGestures;->dp(F)F
|
||||||
|
|
||||||
|
move-result v2
|
||||||
|
|
||||||
|
cmpg-float v3, v0, v2
|
||||||
|
|
||||||
|
if-gez v3, :move_axis
|
||||||
|
|
||||||
|
goto :done
|
||||||
|
|
||||||
|
:move_axis
|
||||||
|
const v2, 0x3f8ccccd # 1.1f horizontal dominance while dragging
|
||||||
|
|
||||||
|
mul-float/2addr v1, v2
|
||||||
|
|
||||||
|
cmpg-float v2, v0, v1
|
||||||
|
|
||||||
|
if-gez v2, :accept
|
||||||
|
|
||||||
|
goto :done
|
||||||
|
|
||||||
|
:accept
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerGestures;->lockHorizontalGesture()Z
|
||||||
|
|
||||||
|
move-result v2
|
||||||
|
|
||||||
|
if-eqz v2, :cancel_locked
|
||||||
|
|
||||||
|
const/4 v2, 0x1
|
||||||
|
|
||||||
|
sput-boolean v2, Lradiant/MiniPlayerTrackGestures;->h:Z
|
||||||
|
|
||||||
|
:publish
|
||||||
|
const/4 v2, 0x0
|
||||||
|
|
||||||
|
invoke-static {v2}, Lradiant/MiniPlayerGestures;->setSwipeFeedbackDirect(F)V
|
||||||
|
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerTrackGestures;->suppressTapOpen()V
|
||||||
|
|
||||||
|
invoke-static {p0}, Lradiant/MiniPlayerTrackGestures;->setDragOffset(F)V
|
||||||
|
|
||||||
|
:done
|
||||||
|
return-void
|
||||||
|
|
||||||
|
:cancel_locked
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
sput-boolean v0, Lradiant/MiniPlayerTrackGestures;->g:Z
|
||||||
|
|
||||||
|
sput-boolean v0, Lradiant/MiniPlayerTrackGestures;->h:Z
|
||||||
|
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerTrackGestures;->animateReset()V
|
||||||
|
|
||||||
|
goto :done
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method private static dp(F)F
|
||||||
|
.locals 1
|
||||||
|
|
||||||
|
invoke-static {}, Landroid/content/res/Resources;->getSystem()Landroid/content/res/Resources;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Landroid/content/res/Resources;->getDisplayMetrics()Landroid/util/DisplayMetrics;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
iget v0, v0, Landroid/util/DisplayMetrics;->density:F
|
||||||
|
|
||||||
|
mul-float/2addr p0, v0
|
||||||
|
|
||||||
|
return p0
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public static feedbackModifier(Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier;
|
||||||
|
.locals 2
|
||||||
|
|
||||||
|
const/high16 v0, 0x3f800000 # 1.0f
|
||||||
|
|
||||||
|
invoke-static {p0, v0}, Landroidx/compose/ui/ZIndexModifierKt;->zIndex(Landroidx/compose/ui/Modifier;F)Landroidx/compose/ui/Modifier;
|
||||||
|
|
||||||
|
move-result-object p0
|
||||||
|
|
||||||
|
sget-object v0, Lradiant/MiniPlayerTrackGestures$OffsetLayer;->INSTANCE:Lradiant/MiniPlayerTrackGestures$OffsetLayer;
|
||||||
|
|
||||||
|
invoke-static {p0, v0}, Landroidx/compose/ui/graphics/GraphicsLayerModifierKt;->graphicsLayer(Landroidx/compose/ui/Modifier;Lyl0/l;)Landroidx/compose/ui/Modifier;
|
||||||
|
|
||||||
|
move-result-object p0
|
||||||
|
|
||||||
|
return-object p0
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public static textFeedbackModifier(Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier;
|
||||||
|
.locals 1
|
||||||
|
|
||||||
|
sget-object v0, Lradiant/MiniPlayerTrackGestures$TextDraw;->INSTANCE:Lradiant/MiniPlayerTrackGestures$TextDraw; # text clip on right only
|
||||||
|
|
||||||
|
invoke-static {p0, v0}, Landroidx/compose/ui/draw/DrawModifierKt;->drawWithContent(Landroidx/compose/ui/Modifier;Lyl0/l;)Landroidx/compose/ui/Modifier;
|
||||||
|
|
||||||
|
move-result-object p0
|
||||||
|
|
||||||
|
return-object p0
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public static onRenderedItem(Ljava/lang/Object;)V
|
||||||
|
.locals 2
|
||||||
|
|
||||||
|
sget-object v0, Lradiant/MiniPlayerTrackGestures;->d:Ljava/lang/Object;
|
||||||
|
|
||||||
|
if-eqz v0, :store_initial
|
||||||
|
|
||||||
|
invoke-virtual {v0, p0}, Ljava/lang/Object;->equals(Ljava/lang/Object;)Z
|
||||||
|
|
||||||
|
move-result v1
|
||||||
|
|
||||||
|
if-nez v1, :done
|
||||||
|
|
||||||
|
sput-object p0, Lradiant/MiniPlayerTrackGestures;->d:Ljava/lang/Object;
|
||||||
|
|
||||||
|
invoke-static {}, Lradiant/MiniPlayerTrackGestures;->animateReset()V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
|
||||||
|
:store_initial
|
||||||
|
sput-object p0, Lradiant/MiniPlayerTrackGestures;->d:Ljava/lang/Object;
|
||||||
|
|
||||||
|
:done
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public static consumeTapOpenSuppression()Z
|
||||||
|
.locals 5
|
||||||
|
|
||||||
|
sget-wide v0, Lradiant/MiniPlayerTrackGestures;->a:J
|
||||||
|
|
||||||
|
const-wide/16 v2, 0x0
|
||||||
|
|
||||||
|
cmp-long v4, v0, v2
|
||||||
|
|
||||||
|
if-nez v4, :check_time
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
return v0
|
||||||
|
|
||||||
|
:check_time
|
||||||
|
sput-wide v2, Lradiant/MiniPlayerTrackGestures;->a:J
|
||||||
|
|
||||||
|
invoke-static {}, Landroid/os/SystemClock;->uptimeMillis()J
|
||||||
|
|
||||||
|
move-result-wide v2
|
||||||
|
|
||||||
|
cmp-long v4, v2, v0
|
||||||
|
|
||||||
|
if-lez v4, :yes
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
return v0
|
||||||
|
|
||||||
|
:yes
|
||||||
|
const/4 v0, 0x1
|
||||||
|
|
||||||
|
return v0
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public static suppressTapOpen()V
|
||||||
|
.locals 4
|
||||||
|
|
||||||
|
invoke-static {}, Landroid/os/SystemClock;->uptimeMillis()J
|
||||||
|
|
||||||
|
move-result-wide v0
|
||||||
|
|
||||||
|
const-wide/16 v2, 0x258
|
||||||
|
|
||||||
|
add-long/2addr v0, v2
|
||||||
|
|
||||||
|
sput-wide v0, Lradiant/MiniPlayerTrackGestures;->a:J
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public static setDragOffset(F)V
|
||||||
|
.locals 5
|
||||||
|
|
||||||
|
sget-object v0, Lradiant/MiniPlayerTrackGestures;->c:Landroid/animation/ValueAnimator;
|
||||||
|
|
||||||
|
if-eqz v0, :rubber_band
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Landroid/animation/ValueAnimator;->cancel()V
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
sput-object v0, Lradiant/MiniPlayerTrackGestures;->c:Landroid/animation/ValueAnimator;
|
||||||
|
|
||||||
|
:rubber_band
|
||||||
|
invoke-static {p0}, Ljava/lang/Math;->abs(F)F
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
const/high16 v1, 0x42800000 # 64.0f dp free travel
|
||||||
|
|
||||||
|
invoke-static {v1}, Lradiant/MiniPlayerTrackGestures;->dp(F)F
|
||||||
|
|
||||||
|
move-result v1
|
||||||
|
|
||||||
|
cmpg-float v2, v0, v1
|
||||||
|
|
||||||
|
if-lez v2, :publish
|
||||||
|
|
||||||
|
sub-float/2addr v0, v1
|
||||||
|
|
||||||
|
const/high16 v2, 0x3e800000 # 0.25f resistance after 64dp
|
||||||
|
|
||||||
|
mul-float/2addr v0, v2
|
||||||
|
|
||||||
|
const v2, 0x3eb33333 # 0.35f max extra travel
|
||||||
|
|
||||||
|
mul-float/2addr v2, v1
|
||||||
|
|
||||||
|
invoke-static {v0, v2}, Ljava/lang/Math;->min(FF)F
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
add-float/2addr v0, v1
|
||||||
|
|
||||||
|
const/4 v1, 0x0
|
||||||
|
|
||||||
|
cmpg-float v2, p0, v1
|
||||||
|
|
||||||
|
if-gez v2, :positive
|
||||||
|
|
||||||
|
neg-float p0, v0
|
||||||
|
|
||||||
|
goto :publish
|
||||||
|
|
||||||
|
:positive
|
||||||
|
move p0, v0
|
||||||
|
|
||||||
|
|
||||||
|
:publish
|
||||||
|
invoke-static {p0}, Lradiant/MiniPlayerTrackGestures;->setDragOffsetDirect(F)V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public static setDragOffsetDirect(F)V
|
||||||
|
.locals 1
|
||||||
|
|
||||||
|
sget-object v0, Lradiant/MiniPlayerTrackGestures;->b:Landroidx/compose/runtime/MutableFloatState;
|
||||||
|
|
||||||
|
invoke-interface {v0, p0}, Landroidx/compose/runtime/MutableFloatState;->setFloatValue(F)V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public static trackModifier(Landroidx/compose/ui/Modifier;Lyl0/l;)Landroidx/compose/ui/Modifier;
|
||||||
|
.locals 1
|
||||||
|
|
||||||
|
new-instance v0, Lradiant/MiniPlayerTrackGestures$Gesture;
|
||||||
|
|
||||||
|
invoke-direct {v0, p1}, Lradiant/MiniPlayerTrackGestures$Gesture;-><init>(Lyl0/l;)V
|
||||||
|
|
||||||
|
invoke-static {p0, v0}, Landroidx/compose/ui/input/pointer/PointerInteropFilter_androidKt;->motionEventSpy(Landroidx/compose/ui/Modifier;Lyl0/l;)Landroidx/compose/ui/Modifier;
|
||||||
|
|
||||||
|
move-result-object p0
|
||||||
|
|
||||||
|
return-object p0
|
||||||
|
.end method
|
||||||
@@ -0,0 +1,436 @@
|
|||||||
|
.class public final Lradiant/MiniSeekerFloating;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
|
||||||
|
.implements Lyl0/l;
|
||||||
|
|
||||||
|
|
||||||
|
# static fields
|
||||||
|
.field public static final INSTANCE:Lradiant/MiniSeekerFloating;
|
||||||
|
|
||||||
|
|
||||||
|
# direct methods
|
||||||
|
.method static constructor <clinit>()V
|
||||||
|
.locals 1
|
||||||
|
|
||||||
|
new-instance v0, Lradiant/MiniSeekerFloating; # singleton
|
||||||
|
|
||||||
|
invoke-direct {v0}, Lradiant/MiniSeekerFloating;-><init>()V # ctor
|
||||||
|
|
||||||
|
sput-object v0, Lradiant/MiniSeekerFloating;->INSTANCE:Lradiant/MiniSeekerFloating; # publish
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
.method private constructor <init>()V
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
.method public final invoke(Ljava/lang/Object;)Ljava/lang/Object;
|
||||||
|
.locals 2
|
||||||
|
|
||||||
|
check-cast p1, Landroidx/compose/ui/graphics/drawscope/ContentDrawScope; # narrow type
|
||||||
|
|
||||||
|
invoke-interface {p1}, Landroidx/compose/ui/graphics/drawscope/ContentDrawScope;->drawContent()V # draw pill content
|
||||||
|
|
||||||
|
invoke-static {}, Lradiant/MiniSeekerLine;->start()V # lazy subscribe
|
||||||
|
|
||||||
|
:try_start
|
||||||
|
invoke-static {p1}, Lradiant/MiniSeekerFloating;->drawBorder(Landroidx/compose/ui/graphics/drawscope/ContentDrawScope;)V # draw progress
|
||||||
|
:try_end
|
||||||
|
.catch Ljava/lang/Throwable; {:try_start .. :try_end} :swallow
|
||||||
|
|
||||||
|
:swallow
|
||||||
|
sget-object v0, Lkotlin/u;->a:Lkotlin/u; # Unit
|
||||||
|
|
||||||
|
return-object v0
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
.method private static drawBorder(Landroidx/compose/ui/graphics/drawscope/ContentDrawScope;)V
|
||||||
|
.locals 60
|
||||||
|
|
||||||
|
sget-object v0, Lradiant/MiniSeekerLine;->progressState:Landroidx/compose/runtime/MutableFloatState; # state
|
||||||
|
|
||||||
|
invoke-interface {v0}, Landroidx/compose/runtime/MutableFloatState;->getFloatValue()F # read tracked
|
||||||
|
|
||||||
|
move-result v0 # raw progress
|
||||||
|
|
||||||
|
const/4 v1, 0x0 # lower bound
|
||||||
|
|
||||||
|
invoke-static {v0, v1}, Ljava/lang/Math;->max(FF)F # clamp low
|
||||||
|
|
||||||
|
move-result v0 # clamped low
|
||||||
|
|
||||||
|
const/high16 v1, 0x3f800000 # 1.0f upper
|
||||||
|
|
||||||
|
invoke-static {v0, v1}, Ljava/lang/Math;->min(FF)F # clamp high
|
||||||
|
|
||||||
|
move-result v0 # final progress
|
||||||
|
|
||||||
|
const/4 v1, 0x0 # zero
|
||||||
|
|
||||||
|
cmpg-float v2, v0, v1 # compare to zero
|
||||||
|
|
||||||
|
if-lez v2, :done # skip if empty
|
||||||
|
|
||||||
|
move/from16 v16, v0 # stash progress
|
||||||
|
|
||||||
|
invoke-interface/range {p0 .. p0}, Landroidx/compose/ui/graphics/drawscope/DrawScope;->getSize-NH-jbRc()J # size long
|
||||||
|
|
||||||
|
move-result-wide v0 # size pair
|
||||||
|
|
||||||
|
invoke-static {v0, v1}, Landroidx/compose/ui/geometry/Size;->getWidth-impl(J)F # extract width
|
||||||
|
|
||||||
|
move-result v2 # width px
|
||||||
|
|
||||||
|
move/from16 v17, v2 # stash width
|
||||||
|
|
||||||
|
invoke-static {v0, v1}, Landroidx/compose/ui/geometry/Size;->getHeight-impl(J)F # extract height
|
||||||
|
|
||||||
|
move-result v2 # height px
|
||||||
|
|
||||||
|
move/from16 v18, v2 # stash height
|
||||||
|
|
||||||
|
invoke-interface/range {p0 .. p0}, Landroidx/compose/ui/unit/Density;->getDensity()F # density
|
||||||
|
|
||||||
|
move-result v2 # px per dp
|
||||||
|
|
||||||
|
move/from16 v19, v2 # stash density
|
||||||
|
|
||||||
|
const/high16 v0, 0x42100000 # 36.0f dp
|
||||||
|
|
||||||
|
move/from16 v1, v19 # density
|
||||||
|
|
||||||
|
mul-float v2, v0, v1 # to px
|
||||||
|
|
||||||
|
move/from16 v20, v2 # stash r
|
||||||
|
|
||||||
|
const/high16 v0, 0x40000000 # 2.0f
|
||||||
|
|
||||||
|
move/from16 v1, v20 # r
|
||||||
|
|
||||||
|
mul-float v2, v1, v0 # 2*r
|
||||||
|
|
||||||
|
move/from16 v21, v2 # stash 2r
|
||||||
|
|
||||||
|
move/from16 v0, v17 # w
|
||||||
|
|
||||||
|
move/from16 v1, v20 # r
|
||||||
|
|
||||||
|
sub-float v2, v0, v1 # w-r
|
||||||
|
|
||||||
|
move/from16 v22, v2 # stash w-r
|
||||||
|
|
||||||
|
move/from16 v0, v18 # h
|
||||||
|
|
||||||
|
move/from16 v1, v20 # r
|
||||||
|
|
||||||
|
sub-float v2, v0, v1 # h-r
|
||||||
|
|
||||||
|
move/from16 v23, v2 # stash h-r
|
||||||
|
|
||||||
|
move/from16 v0, v17 # w
|
||||||
|
|
||||||
|
move/from16 v1, v21 # 2r
|
||||||
|
|
||||||
|
sub-float v2, v0, v1 # w-2r
|
||||||
|
|
||||||
|
move/from16 v24, v2 # stash w-2r
|
||||||
|
|
||||||
|
move/from16 v0, v18 # h
|
||||||
|
|
||||||
|
move/from16 v1, v21 # 2r
|
||||||
|
|
||||||
|
sub-float v2, v0, v1 # h-2r
|
||||||
|
|
||||||
|
move/from16 v25, v2 # stash h-2r
|
||||||
|
|
||||||
|
move/from16 v0, v17 # w
|
||||||
|
|
||||||
|
const/high16 v1, 0x40000000 # 2.0f
|
||||||
|
|
||||||
|
div-float v2, v0, v1 # w/2
|
||||||
|
|
||||||
|
move/from16 v26, v2 # stash top-center x
|
||||||
|
|
||||||
|
const/high16 v0, 0x42b40000 # 90.0f
|
||||||
|
|
||||||
|
move/from16 v27, v0 # stash 90
|
||||||
|
|
||||||
|
const/4 v0, 0x0 # zero
|
||||||
|
|
||||||
|
move/from16 v1, v27 # 90
|
||||||
|
|
||||||
|
sub-float v2, v0, v1 # -90
|
||||||
|
|
||||||
|
move/from16 v28, v2 # stash -90
|
||||||
|
|
||||||
|
const/high16 v0, 0x43340000 # 180.0f
|
||||||
|
|
||||||
|
move/from16 v29, v0 # stash 180
|
||||||
|
|
||||||
|
invoke-static {}, Landroidx/compose/ui/graphics/AndroidPath_androidKt;->Path()Landroidx/compose/ui/graphics/Path; # new path
|
||||||
|
|
||||||
|
move-result-object v0 # path
|
||||||
|
|
||||||
|
move-object/from16 v30, v0 # stash path
|
||||||
|
|
||||||
|
move-object/from16 v0, v30 # path
|
||||||
|
|
||||||
|
move/from16 v1, v26 # w/2
|
||||||
|
|
||||||
|
const/4 v2, 0x0 # 0
|
||||||
|
|
||||||
|
invoke-interface {v0, v1, v2}, Landroidx/compose/ui/graphics/Path;->moveTo(FF)V # start top-center
|
||||||
|
|
||||||
|
move-object/from16 v0, v30 # path
|
||||||
|
|
||||||
|
move/from16 v1, v22 # w-r
|
||||||
|
|
||||||
|
const/4 v2, 0x0 # 0
|
||||||
|
|
||||||
|
invoke-interface {v0, v1, v2}, Landroidx/compose/ui/graphics/Path;->lineTo(FF)V # top edge right
|
||||||
|
|
||||||
|
new-instance v3, Landroidx/compose/ui/geometry/Rect; # arc bounds
|
||||||
|
|
||||||
|
move/from16 v4, v24 # w-2r
|
||||||
|
|
||||||
|
const/4 v5, 0x0 # 0
|
||||||
|
|
||||||
|
move/from16 v6, v17 # w
|
||||||
|
|
||||||
|
move/from16 v7, v21 # 2r
|
||||||
|
|
||||||
|
invoke-direct {v3, v4, v5, v6, v7}, Landroidx/compose/ui/geometry/Rect;-><init>(FFFF)V # TR rect
|
||||||
|
|
||||||
|
move-object/from16 v0, v30 # path
|
||||||
|
|
||||||
|
move/from16 v4, v28 # -90
|
||||||
|
|
||||||
|
move/from16 v5, v27 # 90
|
||||||
|
|
||||||
|
const/4 v6, 0x0 # false
|
||||||
|
|
||||||
|
invoke-interface {v0, v3, v4, v5, v6}, Landroidx/compose/ui/graphics/Path;->arcTo(Landroidx/compose/ui/geometry/Rect;FFZ)V # TR corner
|
||||||
|
|
||||||
|
move-object/from16 v0, v30 # path
|
||||||
|
|
||||||
|
move/from16 v1, v17 # w
|
||||||
|
|
||||||
|
move/from16 v2, v23 # h-r
|
||||||
|
|
||||||
|
invoke-interface {v0, v1, v2}, Landroidx/compose/ui/graphics/Path;->lineTo(FF)V # right edge
|
||||||
|
|
||||||
|
new-instance v3, Landroidx/compose/ui/geometry/Rect; # arc bounds
|
||||||
|
|
||||||
|
move/from16 v4, v24 # w-2r
|
||||||
|
|
||||||
|
move/from16 v5, v25 # h-2r
|
||||||
|
|
||||||
|
move/from16 v6, v17 # w
|
||||||
|
|
||||||
|
move/from16 v7, v18 # h
|
||||||
|
|
||||||
|
invoke-direct {v3, v4, v5, v6, v7}, Landroidx/compose/ui/geometry/Rect;-><init>(FFFF)V # BR rect
|
||||||
|
|
||||||
|
move-object/from16 v0, v30 # path
|
||||||
|
|
||||||
|
const/4 v4, 0x0 # 0 deg
|
||||||
|
|
||||||
|
move/from16 v5, v27 # 90
|
||||||
|
|
||||||
|
const/4 v6, 0x0 # false
|
||||||
|
|
||||||
|
invoke-interface {v0, v3, v4, v5, v6}, Landroidx/compose/ui/graphics/Path;->arcTo(Landroidx/compose/ui/geometry/Rect;FFZ)V # BR corner
|
||||||
|
|
||||||
|
move-object/from16 v0, v30 # path
|
||||||
|
|
||||||
|
move/from16 v1, v20 # r
|
||||||
|
|
||||||
|
move/from16 v2, v18 # h
|
||||||
|
|
||||||
|
invoke-interface {v0, v1, v2}, Landroidx/compose/ui/graphics/Path;->lineTo(FF)V # bottom edge
|
||||||
|
|
||||||
|
new-instance v3, Landroidx/compose/ui/geometry/Rect; # arc bounds
|
||||||
|
|
||||||
|
const/4 v4, 0x0 # 0
|
||||||
|
|
||||||
|
move/from16 v5, v25 # h-2r
|
||||||
|
|
||||||
|
move/from16 v6, v21 # 2r
|
||||||
|
|
||||||
|
move/from16 v7, v18 # h
|
||||||
|
|
||||||
|
invoke-direct {v3, v4, v5, v6, v7}, Landroidx/compose/ui/geometry/Rect;-><init>(FFFF)V # BL rect
|
||||||
|
|
||||||
|
move-object/from16 v0, v30 # path
|
||||||
|
|
||||||
|
move/from16 v4, v27 # 90
|
||||||
|
|
||||||
|
move/from16 v5, v27 # 90
|
||||||
|
|
||||||
|
const/4 v6, 0x0 # false
|
||||||
|
|
||||||
|
invoke-interface {v0, v3, v4, v5, v6}, Landroidx/compose/ui/graphics/Path;->arcTo(Landroidx/compose/ui/geometry/Rect;FFZ)V # BL corner
|
||||||
|
|
||||||
|
move-object/from16 v0, v30 # path
|
||||||
|
|
||||||
|
const/4 v1, 0x0 # 0
|
||||||
|
|
||||||
|
move/from16 v2, v20 # r
|
||||||
|
|
||||||
|
invoke-interface {v0, v1, v2}, Landroidx/compose/ui/graphics/Path;->lineTo(FF)V # left edge
|
||||||
|
|
||||||
|
new-instance v3, Landroidx/compose/ui/geometry/Rect; # arc bounds
|
||||||
|
|
||||||
|
const/4 v4, 0x0 # 0
|
||||||
|
|
||||||
|
const/4 v5, 0x0 # 0
|
||||||
|
|
||||||
|
move/from16 v6, v21 # 2r
|
||||||
|
|
||||||
|
move/from16 v7, v21 # 2r
|
||||||
|
|
||||||
|
invoke-direct {v3, v4, v5, v6, v7}, Landroidx/compose/ui/geometry/Rect;-><init>(FFFF)V # TL rect
|
||||||
|
|
||||||
|
move-object/from16 v0, v30 # path
|
||||||
|
|
||||||
|
move/from16 v4, v29 # 180
|
||||||
|
|
||||||
|
move/from16 v5, v27 # 90
|
||||||
|
|
||||||
|
const/4 v6, 0x0 # false
|
||||||
|
|
||||||
|
invoke-interface {v0, v3, v4, v5, v6}, Landroidx/compose/ui/graphics/Path;->arcTo(Landroidx/compose/ui/geometry/Rect;FFZ)V # TL corner
|
||||||
|
|
||||||
|
move-object/from16 v0, v30 # path
|
||||||
|
|
||||||
|
move/from16 v1, v26 # w/2
|
||||||
|
|
||||||
|
const/4 v2, 0x0 # 0
|
||||||
|
|
||||||
|
invoke-interface {v0, v1, v2}, Landroidx/compose/ui/graphics/Path;->lineTo(FF)V # close to start
|
||||||
|
|
||||||
|
const/high16 v0, 0x40000000 # 2.0f
|
||||||
|
|
||||||
|
move/from16 v1, v24 # w-2r
|
||||||
|
|
||||||
|
mul-float v2, v1, v0 # 2*(w-2r)
|
||||||
|
|
||||||
|
move/from16 v1, v25 # h-2r
|
||||||
|
|
||||||
|
mul-float v3, v1, v0 # 2*(h-2r)
|
||||||
|
|
||||||
|
add-float v4, v2, v3 # straight edges
|
||||||
|
|
||||||
|
const v5, 0x40c90fdb # 2*pi
|
||||||
|
|
||||||
|
move/from16 v6, v20 # r
|
||||||
|
|
||||||
|
mul-float v7, v6, v5 # arc length
|
||||||
|
|
||||||
|
add-float v8, v4, v7 # total perim
|
||||||
|
|
||||||
|
move/from16 v31, v8 # stash perim
|
||||||
|
|
||||||
|
move/from16 v0, v16 # progress
|
||||||
|
|
||||||
|
move/from16 v1, v31 # perim
|
||||||
|
|
||||||
|
mul-float v2, v0, v1 # visible length
|
||||||
|
|
||||||
|
move/from16 v32, v2 # stash visible
|
||||||
|
|
||||||
|
sget-object v0, Landroidx/compose/ui/graphics/PathEffect;->Companion:Landroidx/compose/ui/graphics/PathEffect$Companion; # companion
|
||||||
|
|
||||||
|
const/4 v1, 0x2 # length 2
|
||||||
|
|
||||||
|
new-array v1, v1, [F # intervals
|
||||||
|
|
||||||
|
const/4 v2, 0x0 # index 0
|
||||||
|
|
||||||
|
move/from16 v3, v32 # visible
|
||||||
|
|
||||||
|
aput v3, v1, v2 # dash length
|
||||||
|
|
||||||
|
const/4 v2, 0x1 # index 1
|
||||||
|
|
||||||
|
move/from16 v3, v31 # perim
|
||||||
|
|
||||||
|
aput v3, v1, v2 # gap length
|
||||||
|
|
||||||
|
const/4 v2, 0x0 # phase int
|
||||||
|
|
||||||
|
int-to-float v2, v2 # phase float
|
||||||
|
|
||||||
|
invoke-virtual {v0, v1, v2}, Landroidx/compose/ui/graphics/PathEffect$Companion;->dashPathEffect([FF)Landroidx/compose/ui/graphics/PathEffect; # dash effect
|
||||||
|
|
||||||
|
move-result-object v0 # effect
|
||||||
|
|
||||||
|
move-object/from16 v33, v0 # stash effect
|
||||||
|
|
||||||
|
const/high16 v0, 0x40000000 # 2.0f
|
||||||
|
|
||||||
|
move/from16 v1, v19 # density
|
||||||
|
|
||||||
|
mul-float v2, v0, v1 # stroke px
|
||||||
|
|
||||||
|
move/from16 v34, v2 # stash stroke width
|
||||||
|
|
||||||
|
new-instance v40, Landroidx/compose/ui/graphics/drawscope/Stroke; # stroke obj
|
||||||
|
|
||||||
|
move/from16 v41, v34 # width
|
||||||
|
|
||||||
|
const/16 v42, 0x0 # miter default
|
||||||
|
|
||||||
|
const/16 v43, 0x0 # cap default
|
||||||
|
|
||||||
|
const/16 v44, 0x0 # join default
|
||||||
|
|
||||||
|
move-object/from16 v45, v33 # effect
|
||||||
|
|
||||||
|
const/16 v46, 0xe # default mask
|
||||||
|
|
||||||
|
const/16 v47, 0x0 # marker null
|
||||||
|
|
||||||
|
invoke-direct/range {v40 .. v47}, Landroidx/compose/ui/graphics/drawscope/Stroke;-><init>(FFIILandroidx/compose/ui/graphics/PathEffect;ILkotlin/jvm/internal/DefaultConstructorMarker;)V # stroke ctor
|
||||||
|
|
||||||
|
move-object/from16 v35, v40 # stash stroke
|
||||||
|
|
||||||
|
const v0, -0x1 # white ARGB
|
||||||
|
|
||||||
|
invoke-static {v0}, Landroidx/compose/ui/graphics/ColorKt;->Color(I)J # pack color
|
||||||
|
|
||||||
|
move-result-wide v0 # color long
|
||||||
|
|
||||||
|
move-wide/from16 v36, v0 # stash color
|
||||||
|
|
||||||
|
move-object/from16 v50, p0 # scope
|
||||||
|
|
||||||
|
move-object/from16 v51, v30 # path
|
||||||
|
|
||||||
|
move-wide/from16 v52, v36 # color
|
||||||
|
|
||||||
|
const/16 v54, 0x0 # alpha default
|
||||||
|
|
||||||
|
move-object/from16 v55, v35 # style
|
||||||
|
|
||||||
|
const/16 v56, 0x0 # filter null
|
||||||
|
|
||||||
|
const/16 v57, 0x0 # blend default
|
||||||
|
|
||||||
|
const/16 v58, 0x34 # default mask
|
||||||
|
|
||||||
|
const/16 v59, 0x0 # marker null
|
||||||
|
|
||||||
|
invoke-static/range {v50 .. v59}, Landroidx/compose/ui/graphics/drawscope/DrawScope;->drawPath-LG529CI$default(Landroidx/compose/ui/graphics/drawscope/DrawScope;Landroidx/compose/ui/graphics/Path;JFLandroidx/compose/ui/graphics/drawscope/DrawStyle;Landroidx/compose/ui/graphics/ColorFilter;IILjava/lang/Object;)V # draw stroke
|
||||||
|
|
||||||
|
:done
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
.class public final Lradiant/MiniSeekerLine;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
|
||||||
|
|
||||||
|
# static fields
|
||||||
|
.field public static progressState:Landroidx/compose/runtime/MutableFloatState;
|
||||||
|
|
||||||
|
.field public static volatile vm:Ljh/h;
|
||||||
|
|
||||||
|
.field public static volatile sub:Lio/reactivex/disposables/Disposable;
|
||||||
|
|
||||||
|
|
||||||
|
# direct methods
|
||||||
|
.method static constructor <clinit>()V
|
||||||
|
.locals 1
|
||||||
|
|
||||||
|
const/4 v0, 0x0 # initial progress
|
||||||
|
|
||||||
|
invoke-static {v0}, Landroidx/compose/runtime/PrimitiveSnapshotStateKt;->mutableFloatStateOf(F)Landroidx/compose/runtime/MutableFloatState; # state holder
|
||||||
|
|
||||||
|
move-result-object v0 # state instance
|
||||||
|
|
||||||
|
sput-object v0, Lradiant/MiniSeekerLine;->progressState:Landroidx/compose/runtime/MutableFloatState; # publish state
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
.method private constructor <init>()V
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
.method private static dlog(Ljava/lang/String;)V
|
||||||
|
.locals 1
|
||||||
|
|
||||||
|
const-string v0, "RLProgressLine" # log tag
|
||||||
|
|
||||||
|
invoke-static {v0, p0}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I # info log
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
.method public static start()V
|
||||||
|
.locals 3
|
||||||
|
|
||||||
|
sget-object v0, Lradiant/MiniSeekerLine;->sub:Lio/reactivex/disposables/Disposable; # current sub
|
||||||
|
|
||||||
|
if-eqz v0, :start_block # only run once
|
||||||
|
|
||||||
|
return-void
|
||||||
|
|
||||||
|
:start_block
|
||||||
|
:try_start
|
||||||
|
sget-object v0, Lradiant/MiniSeekerLine;->vm:Ljh/h; # cached vm
|
||||||
|
|
||||||
|
if-nez v0, :got_vm # reuse if present
|
||||||
|
|
||||||
|
invoke-static {}, Lcom/aspiro/wamp/App$a;->a()Lcom/aspiro/wamp/App; # app singleton
|
||||||
|
|
||||||
|
move-result-object v0 # app
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Lcom/aspiro/wamp/App;->e()Lf5/c; # dagger component
|
||||||
|
|
||||||
|
move-result-object v0 # component
|
||||||
|
|
||||||
|
check-cast v0, Lf5/d0$n2; # narrow type
|
||||||
|
|
||||||
|
iget-object v0, v0, Lf5/d0$n2;->z0:Ldagger/internal/j; # playqueue provider
|
||||||
|
|
||||||
|
invoke-interface {v0}, Lol0/a;->get()Ljava/lang/Object; # resolve
|
||||||
|
|
||||||
|
move-result-object v0 # playqueue
|
||||||
|
|
||||||
|
check-cast v0, Lcom/aspiro/wamp/playqueue/z0; # narrow type
|
||||||
|
|
||||||
|
new-instance v1, Ljh/h; # build vm
|
||||||
|
|
||||||
|
invoke-direct {v1, v0}, Ljh/h;-><init>(Lcom/aspiro/wamp/playqueue/z0;)V # ctor
|
||||||
|
|
||||||
|
sput-object v1, Lradiant/MiniSeekerLine;->vm:Ljh/h; # cache
|
||||||
|
|
||||||
|
:got_vm
|
||||||
|
sget-object v0, Lradiant/MiniSeekerLine;->vm:Ljh/h; # vm ref
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Ljh/h;->a()V # activate emission
|
||||||
|
|
||||||
|
iget-object v0, v0, Ljh/h;->d:Lio/reactivex/subjects/BehaviorSubject; # position subject
|
||||||
|
|
||||||
|
invoke-static {}, Lio/reactivex/android/schedulers/AndroidSchedulers;->mainThread()Lio/reactivex/Scheduler; # main scheduler
|
||||||
|
|
||||||
|
move-result-object v1 # scheduler
|
||||||
|
|
||||||
|
invoke-virtual {v0, v1}, Lio/reactivex/Observable;->observeOn(Lio/reactivex/Scheduler;)Lio/reactivex/Observable; # observe main
|
||||||
|
|
||||||
|
move-result-object v0 # observable
|
||||||
|
|
||||||
|
new-instance v1, Lradiant/SeekerConsumer; # consumer
|
||||||
|
|
||||||
|
invoke-direct {v1}, Lradiant/SeekerConsumer;-><init>()V # ctor
|
||||||
|
|
||||||
|
invoke-virtual {v0, v1}, Lio/reactivex/Observable;->subscribe(Lio/reactivex/functions/Consumer;)Lio/reactivex/disposables/Disposable; # subscribe
|
||||||
|
|
||||||
|
move-result-object v0 # disposable
|
||||||
|
|
||||||
|
sput-object v0, Lradiant/MiniSeekerLine;->sub:Lio/reactivex/disposables/Disposable; # cache
|
||||||
|
|
||||||
|
const-string v0, "started" # log msg
|
||||||
|
|
||||||
|
invoke-static {v0}, Lradiant/MiniSeekerLine;->dlog(Ljava/lang/String;)V # info log
|
||||||
|
|
||||||
|
return-void
|
||||||
|
:try_end
|
||||||
|
.catch Ljava/lang/Throwable; {:try_start .. :try_end} :swallow
|
||||||
|
|
||||||
|
:swallow
|
||||||
|
move-exception v0 # error
|
||||||
|
|
||||||
|
const-string v1, "start failed" # log msg
|
||||||
|
|
||||||
|
invoke-static {v1}, Lradiant/MiniSeekerLine;->dlog(Ljava/lang/String;)V # info log
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
.method public static render(Landroidx/compose/runtime/Composer;I)V
|
||||||
|
.locals 5
|
||||||
|
.annotation build Landroidx/compose/runtime/Composable;
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
invoke-static {}, Lradiant/MiniSeekerLine;->start()V # lazy subscribe
|
||||||
|
|
||||||
|
sget-object v0, Lradiant/MiniSeekerLine;->progressState:Landroidx/compose/runtime/MutableFloatState; # state
|
||||||
|
|
||||||
|
invoke-interface {v0}, Landroidx/compose/runtime/MutableFloatState;->getFloatValue()F # read tracked
|
||||||
|
|
||||||
|
move-result v0 # raw progress
|
||||||
|
|
||||||
|
const/4 v1, 0x0 # lower bound
|
||||||
|
|
||||||
|
invoke-static {v0, v1}, Ljava/lang/Math;->max(FF)F # clamp low
|
||||||
|
|
||||||
|
move-result v0 # clamped low
|
||||||
|
|
||||||
|
const/high16 v1, 0x3f800000 # 1.0f upper
|
||||||
|
|
||||||
|
invoke-static {v0, v1}, Ljava/lang/Math;->min(FF)F # clamp high
|
||||||
|
|
||||||
|
move-result v0 # final progress
|
||||||
|
|
||||||
|
const v1, 0x52414c49 # 'RALI' slot key
|
||||||
|
|
||||||
|
invoke-interface {p0, v1}, Landroidx/compose/runtime/Composer;->startReplaceGroup(I)V # open group
|
||||||
|
|
||||||
|
sget-object v1, Landroidx/compose/ui/Modifier;->Companion:Landroidx/compose/ui/Modifier$Companion; # base modifier
|
||||||
|
|
||||||
|
invoke-static {v1, v0}, Landroidx/compose/foundation/layout/SizeKt;->fillMaxWidth(Landroidx/compose/ui/Modifier;F)Landroidx/compose/ui/Modifier; # width fraction
|
||||||
|
|
||||||
|
move-result-object v0 # filled modifier
|
||||||
|
|
||||||
|
const/4 v1, 0x2 # 2 dp
|
||||||
|
|
||||||
|
int-to-float v1, v1 # to float
|
||||||
|
|
||||||
|
invoke-static {v1}, Landroidx/compose/ui/unit/Dp;->constructor-impl(F)F # box as Dp
|
||||||
|
|
||||||
|
move-result v1 # dp value
|
||||||
|
|
||||||
|
invoke-static {v0, v1}, Landroidx/compose/foundation/layout/SizeKt;->height-3ABfNKs(Landroidx/compose/ui/Modifier;F)Landroidx/compose/ui/Modifier; # 2dp tall
|
||||||
|
|
||||||
|
move-result-object v0 # sized modifier
|
||||||
|
|
||||||
|
const v1, -0x1 # 0xFFFFFFFF white
|
||||||
|
|
||||||
|
invoke-static {v1}, Landroidx/compose/ui/graphics/ColorKt;->Color(I)J # pack color
|
||||||
|
|
||||||
|
move-result-wide v1 # color long
|
||||||
|
|
||||||
|
invoke-static {}, Landroidx/compose/ui/graphics/RectangleShapeKt;->getRectangleShape()Landroidx/compose/ui/graphics/Shape; # rect shape
|
||||||
|
|
||||||
|
move-result-object v3 # shape
|
||||||
|
|
||||||
|
invoke-static {v0, v1, v2, v3}, Landroidx/compose/foundation/BackgroundKt;->background-bw27NRU(Landroidx/compose/ui/Modifier;JLandroidx/compose/ui/graphics/Shape;)Landroidx/compose/ui/Modifier; # white bg
|
||||||
|
|
||||||
|
move-result-object v0 # final modifier
|
||||||
|
|
||||||
|
const/4 v1, 0x0 # composer $changed
|
||||||
|
|
||||||
|
invoke-static {v0, p0, v1}, Landroidx/compose/foundation/layout/SpacerKt;->Spacer(Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;I)V # draw line
|
||||||
|
|
||||||
|
invoke-interface {p0}, Landroidx/compose/runtime/Composer;->endReplaceGroup()V # close group
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
.class public final Lradiant/NoOp;
|
.class public final Lradiant/NoOp;
|
||||||
.super Ljava/lang/Object;
|
.super Ljava/lang/Object;
|
||||||
.implements Ltl0/a;
|
.implements Lyl0/a;
|
||||||
|
|
||||||
|
|
||||||
# static fields
|
# static fields
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
.class public final Lradiant/QualityBadge;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
|
||||||
|
|
||||||
|
# direct methods
|
||||||
|
.method private constructor <init>()V
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
# qualities : 0 = LOW / HIGH, 1 = LOSSLESS, 2 = HI_RES_LOSSLESS
|
||||||
|
.method private static backgroundArgb(I)I
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
packed-switch p0, :quality_switch
|
||||||
|
|
||||||
|
const p0, 0x66475569 # LOW / HIGH bg
|
||||||
|
return p0
|
||||||
|
|
||||||
|
:lossless
|
||||||
|
const p0, 0x3306b6d4 # LOSSLESS bg
|
||||||
|
return p0
|
||||||
|
|
||||||
|
:hires
|
||||||
|
const p0, 0x40f59e0b # HI_RES_LOSSLESS bg
|
||||||
|
return p0
|
||||||
|
|
||||||
|
:quality_switch
|
||||||
|
.packed-switch 0x1
|
||||||
|
:lossless
|
||||||
|
:hires
|
||||||
|
.end packed-switch
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method private static textArgb(I)I
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
packed-switch p0, :quality_switch
|
||||||
|
|
||||||
|
const p0, -0x1d1710 # LOW / HIGH text
|
||||||
|
return p0
|
||||||
|
|
||||||
|
:lossless
|
||||||
|
const p0, -0x5a0c04 # LOSSLESS text
|
||||||
|
return p0
|
||||||
|
|
||||||
|
:hires
|
||||||
|
const p0, -0x21976 # HI_RES_LOSSLESS text
|
||||||
|
return p0
|
||||||
|
|
||||||
|
:quality_switch
|
||||||
|
.packed-switch 0x1
|
||||||
|
:lossless
|
||||||
|
:hires
|
||||||
|
.end packed-switch
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
.method public static modifier(Landroidx/compose/ui/Modifier;I)Landroidx/compose/ui/Modifier;
|
||||||
|
.locals 3
|
||||||
|
|
||||||
|
invoke-static {p1}, Lradiant/QualityBadge;->backgroundArgb(I)I
|
||||||
|
|
||||||
|
move-result p1
|
||||||
|
invoke-static {p1}, Landroidx/compose/ui/graphics/ColorKt;->Color(I)J
|
||||||
|
move-result-wide v0
|
||||||
|
const/high16 p1, 0x40800000 # 4.0f
|
||||||
|
invoke-static {p1}, Landroidx/compose/ui/unit/Dp;->constructor-impl(F)F
|
||||||
|
move-result p1
|
||||||
|
invoke-static {p1}, Landroidx/compose/foundation/shape/RoundedCornerShapeKt;->RoundedCornerShape-0680j_4(F)Landroidx/compose/foundation/shape/RoundedCornerShape;
|
||||||
|
move-result-object v2
|
||||||
|
invoke-static {p0, v0, v1, v2}, Landroidx/compose/foundation/BackgroundKt;->background-bw27NRU(Landroidx/compose/ui/Modifier;JLandroidx/compose/ui/graphics/Shape;)Landroidx/compose/ui/Modifier;
|
||||||
|
move-result-object p0
|
||||||
|
const/high16 p1, 0x40c00000 # 6.0f
|
||||||
|
invoke-static {p1}, Landroidx/compose/ui/unit/Dp;->constructor-impl(F)F
|
||||||
|
move-result p1
|
||||||
|
const/high16 v0, 0x40000000 # 2.0f
|
||||||
|
invoke-static {v0}, Landroidx/compose/ui/unit/Dp;->constructor-impl(F)F
|
||||||
|
move-result v0
|
||||||
|
invoke-static {p0, p1, v0}, Landroidx/compose/foundation/layout/PaddingKt;->padding-VpY3zN4(Landroidx/compose/ui/Modifier;FF)Landroidx/compose/ui/Modifier;
|
||||||
|
move-result-object p0
|
||||||
|
return-object p0
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public static style(ILn20/e2;)Ln20/e2;
|
||||||
|
.registers 16
|
||||||
|
|
||||||
|
invoke-static {p0}, Lradiant/QualityBadge;->textArgb(I)I
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
new-instance v1, Lc20/a;
|
||||||
|
invoke-direct {v1, v0}, Lc20/a;-><init>(I)V
|
||||||
|
new-instance v0, Lc20/d;
|
||||||
|
const/4 v2, 0x0
|
||||||
|
const/4 v3, 0x0
|
||||||
|
const/4 v4, 0x0
|
||||||
|
const/4 v5, 0x0
|
||||||
|
const/4 v6, 0x0
|
||||||
|
const/4 v7, 0x0
|
||||||
|
const/4 v8, 0x0
|
||||||
|
const/4 v9, 0x0
|
||||||
|
const/4 v10, 0x0
|
||||||
|
const/4 v11, 0x0
|
||||||
|
const/4 v12, 0x0
|
||||||
|
const/16 v13, 0xffe
|
||||||
|
const/4 v14, 0x0
|
||||||
|
invoke-direct/range {v0 .. v14}, Lc20/d;-><init>(Lc20/a;Lc20/a;Lc20/a;Lc20/a;Lc20/a;Lc20/a;Lc20/a;Lc20/a;Lc20/a;Lc20/a;Lc20/a;Lc20/a;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
|
||||||
|
move-object v9, v0
|
||||||
|
move-object/from16 v0, p1
|
||||||
|
const/4 v1, 0x0
|
||||||
|
move-object v2, v9
|
||||||
|
const/4 v3, 0x0
|
||||||
|
const/4 v4, 0x0
|
||||||
|
const/4 v5, 0x0
|
||||||
|
const/16 v6, 0x1d
|
||||||
|
const/4 v7, 0x0
|
||||||
|
invoke-static/range {v0 .. v7}, Ln20/e2;->g(Ln20/e2;Ln20/u4;Lc20/d;Lcom/squareup/ui/market/core/text/MarketTextAlignment;Lcom/squareup/ui/market/core/text/MarketTextTransform;Ln20/t4;ILjava/lang/Object;)Ln20/e2;
|
||||||
|
move-result-object p0
|
||||||
|
return-object p0
|
||||||
|
.end method
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
.class public final Lradiant/RLAPILyricsHook;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
|
||||||
|
|
||||||
|
# static fields
|
||||||
|
.field public static volatile currentKey:Ljava/lang/String;
|
||||||
|
|
||||||
|
.field public static volatile isRlState:Z
|
||||||
|
|
||||||
|
|
||||||
|
# direct methods
|
||||||
|
.method static constructor <clinit>()V
|
||||||
|
.locals 1
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
sput-object v0, Lradiant/RLAPILyricsHook;->currentKey:Ljava/lang/String;
|
||||||
|
|
||||||
|
sput-boolean v0, Lradiant/RLAPILyricsHook;->isRlState:Z
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method private constructor <init>()V
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public static dlog(Ljava/lang/String;)V
|
||||||
|
.locals 1
|
||||||
|
|
||||||
|
const-string v0, "RLLyrics"
|
||||||
|
|
||||||
|
invoke-static {v0, p0}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method private static isBlank(Ljava/lang/String;)Z
|
||||||
|
.locals 2
|
||||||
|
|
||||||
|
if-nez p0, :not_null
|
||||||
|
|
||||||
|
const/4 v0, 0x1
|
||||||
|
|
||||||
|
return v0
|
||||||
|
|
||||||
|
:not_null
|
||||||
|
invoke-virtual {p0}, Ljava/lang/String;->trim()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Ljava/lang/String;->isEmpty()Z
|
||||||
|
|
||||||
|
move-result v1
|
||||||
|
|
||||||
|
return v1
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public static onWampTrack(Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;Lcom/aspiro/wamp/model/Track;)V
|
||||||
|
.locals 11
|
||||||
|
|
||||||
|
invoke-static {p1}, Lradiant/HomeBackdrop;->onTrack(Lcom/aspiro/wamp/model/Track;)V
|
||||||
|
|
||||||
|
const/4 v3, 0x0
|
||||||
|
|
||||||
|
sput-boolean v3, Lradiant/RLAPILyricsHook;->isRlState:Z
|
||||||
|
|
||||||
|
const-string v3, "onWampTrack: hook entered"
|
||||||
|
|
||||||
|
invoke-static {v3}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
if-eqz p1, :null_track
|
||||||
|
|
||||||
|
if-eqz p0, :done
|
||||||
|
|
||||||
|
goto :have_track
|
||||||
|
|
||||||
|
:null_track
|
||||||
|
const-string v3, "onWampTrack: wamp Track is null, skip"
|
||||||
|
|
||||||
|
invoke-static {v3}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
|
||||||
|
:have_track
|
||||||
|
invoke-virtual {p1}, Lcom/aspiro/wamp/model/MediaItem;->getTitle()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v1
|
||||||
|
|
||||||
|
if-nez v1, :title_present
|
||||||
|
|
||||||
|
const-string v3, "bail: getTitle() returned null"
|
||||||
|
|
||||||
|
invoke-static {v3}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
|
||||||
|
:title_present
|
||||||
|
invoke-static {v1}, Lradiant/RLAPILyricsHook;->isBlank(Ljava/lang/String;)Z
|
||||||
|
|
||||||
|
move-result v2
|
||||||
|
|
||||||
|
if-eqz v2, :title_ok
|
||||||
|
|
||||||
|
const-string v3, "bail: title is blank"
|
||||||
|
|
||||||
|
invoke-static {v3}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
|
||||||
|
:title_ok
|
||||||
|
invoke-virtual {p1}, Lcom/aspiro/wamp/model/MediaItem;->getArtistNames()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v2
|
||||||
|
|
||||||
|
if-nez v2, :artist_present
|
||||||
|
|
||||||
|
const-string v3, "bail: getArtistNames() returned null"
|
||||||
|
|
||||||
|
invoke-static {v3}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
|
||||||
|
:artist_present
|
||||||
|
invoke-static {v2}, Lradiant/RLAPILyricsHook;->isBlank(Ljava/lang/String;)Z
|
||||||
|
|
||||||
|
move-result v3
|
||||||
|
|
||||||
|
if-eqz v3, :artist_ok
|
||||||
|
|
||||||
|
const-string v3, "bail: artist is blank"
|
||||||
|
|
||||||
|
invoke-static {v3}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
|
||||||
|
:artist_ok
|
||||||
|
const-string v3, ""
|
||||||
|
|
||||||
|
new-instance v4, Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-direct {v4}, Ljava/lang/StringBuilder;-><init>()V
|
||||||
|
|
||||||
|
invoke-virtual {v4, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
const-string v5, "|"
|
||||||
|
|
||||||
|
invoke-virtual {v4, v5}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v4, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v4}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v4
|
||||||
|
|
||||||
|
sput-object v4, Lradiant/RLAPILyricsHook;->currentKey:Ljava/lang/String;
|
||||||
|
|
||||||
|
new-instance v5, Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
const-string v6, "onWampTrack: fetching for title='"
|
||||||
|
|
||||||
|
invoke-direct {v5, v6}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
invoke-virtual {v5, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
const-string v6, "' artist='"
|
||||||
|
|
||||||
|
invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v5, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
const-string v6, "'"
|
||||||
|
|
||||||
|
invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v5}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v5
|
||||||
|
|
||||||
|
invoke-static {v5}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
new-instance v5, Lradiant/RLAPILyricsWorker;
|
||||||
|
|
||||||
|
move-object v6, p0
|
||||||
|
|
||||||
|
move-object v7, v1
|
||||||
|
|
||||||
|
move-object v8, v2
|
||||||
|
|
||||||
|
move-object v9, v4
|
||||||
|
|
||||||
|
move-object v10, v3
|
||||||
|
|
||||||
|
invoke-direct/range {v5 .. v10}, Lradiant/RLAPILyricsWorker;-><init>(Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
|
||||||
|
|
||||||
|
new-instance v6, Ljava/lang/Thread;
|
||||||
|
|
||||||
|
invoke-direct {v6, v5}, Ljava/lang/Thread;-><init>(Ljava/lang/Runnable;)V
|
||||||
|
|
||||||
|
const/4 v7, 0x1
|
||||||
|
|
||||||
|
invoke-virtual {v6, v7}, Ljava/lang/Thread;->setDaemon(Z)V
|
||||||
|
|
||||||
|
invoke-virtual {v6}, Ljava/lang/Thread;->start()V
|
||||||
|
|
||||||
|
:done
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
@@ -0,0 +1,545 @@
|
|||||||
|
.class public final Lradiant/RLAPILyricsWorker;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
.implements Ljava/lang/Runnable;
|
||||||
|
|
||||||
|
|
||||||
|
# instance fields
|
||||||
|
.field public final vm:Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;
|
||||||
|
|
||||||
|
.field public final title:Ljava/lang/String;
|
||||||
|
|
||||||
|
.field public final artist:Ljava/lang/String;
|
||||||
|
|
||||||
|
.field public final key:Ljava/lang/String;
|
||||||
|
|
||||||
|
.field public final lyricsId:Ljava/lang/String;
|
||||||
|
|
||||||
|
|
||||||
|
# direct methods
|
||||||
|
.method public constructor <init>(Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||||
|
|
||||||
|
iput-object p1, p0, Lradiant/RLAPILyricsWorker;->vm:Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;
|
||||||
|
|
||||||
|
iput-object p2, p0, Lradiant/RLAPILyricsWorker;->title:Ljava/lang/String;
|
||||||
|
|
||||||
|
iput-object p3, p0, Lradiant/RLAPILyricsWorker;->artist:Ljava/lang/String;
|
||||||
|
|
||||||
|
iput-object p4, p0, Lradiant/RLAPILyricsWorker;->key:Ljava/lang/String;
|
||||||
|
|
||||||
|
iput-object p5, p0, Lradiant/RLAPILyricsWorker;->lyricsId:Ljava/lang/String;
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public static fetch(Ljava/lang/String;Z)Ljava/lang/String;
|
||||||
|
.locals 7
|
||||||
|
|
||||||
|
:try_start
|
||||||
|
new-instance v0, Ljava/net/URL;
|
||||||
|
|
||||||
|
invoke-direct {v0, p0}, Ljava/net/URL;-><init>(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Ljava/net/URL;->openConnection()Ljava/net/URLConnection;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
check-cast v0, Ljava/net/HttpURLConnection;
|
||||||
|
|
||||||
|
const-string v1, "GET"
|
||||||
|
|
||||||
|
invoke-virtual {v0, v1}, Ljava/net/HttpURLConnection;->setRequestMethod(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
const v1, 0x2710
|
||||||
|
|
||||||
|
invoke-virtual {v0, v1}, Ljava/net/URLConnection;->setConnectTimeout(I)V
|
||||||
|
|
||||||
|
invoke-virtual {v0, v1}, Ljava/net/URLConnection;->setReadTimeout(I)V
|
||||||
|
|
||||||
|
if-eqz p1, :no_auth
|
||||||
|
|
||||||
|
const-string v1, "P-Access-Token-Id"
|
||||||
|
|
||||||
|
const-string v2, "58hy4s86"
|
||||||
|
|
||||||
|
invoke-virtual {v0, v1, v2}, Ljava/net/URLConnection;->setRequestProperty(Ljava/lang/String;Ljava/lang/String;)V
|
||||||
|
|
||||||
|
const-string v1, "P-Access-Token"
|
||||||
|
|
||||||
|
const-string v2, "xjehy2lfg5h5mjwotoxrcqugam"
|
||||||
|
|
||||||
|
invoke-virtual {v0, v1, v2}, Ljava/net/URLConnection;->setRequestProperty(Ljava/lang/String;Ljava/lang/String;)V
|
||||||
|
|
||||||
|
const-string v1, "x-client-ip"
|
||||||
|
|
||||||
|
const-string v2, "null"
|
||||||
|
|
||||||
|
invoke-virtual {v0, v1, v2}, Ljava/net/URLConnection;->setRequestProperty(Ljava/lang/String;Ljava/lang/String;)V
|
||||||
|
|
||||||
|
:no_auth
|
||||||
|
invoke-virtual {v0}, Ljava/net/HttpURLConnection;->getResponseCode()I
|
||||||
|
|
||||||
|
move-result v1
|
||||||
|
|
||||||
|
new-instance v3, Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
const-string v4, "fetch status="
|
||||||
|
|
||||||
|
invoke-direct {v3, v4}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
invoke-virtual {v3, v1}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
const-string v4, " url="
|
||||||
|
|
||||||
|
invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v3, p0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v3
|
||||||
|
|
||||||
|
invoke-static {v3}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
const/16 v2, 0xc8
|
||||||
|
|
||||||
|
if-eq v1, v2, :status_ok
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Ljava/net/HttpURLConnection;->disconnect()V
|
||||||
|
|
||||||
|
const/4 v1, 0x0
|
||||||
|
|
||||||
|
return-object v1
|
||||||
|
|
||||||
|
:status_ok
|
||||||
|
invoke-virtual {v0}, Ljava/net/HttpURLConnection;->getInputStream()Ljava/io/InputStream;
|
||||||
|
|
||||||
|
move-result-object v1
|
||||||
|
|
||||||
|
new-instance v2, Ljava/io/InputStreamReader;
|
||||||
|
|
||||||
|
const-string v3, "UTF-8"
|
||||||
|
|
||||||
|
invoke-direct {v2, v1, v3}, Ljava/io/InputStreamReader;-><init>(Ljava/io/InputStream;Ljava/lang/String;)V
|
||||||
|
|
||||||
|
new-instance v3, Ljava/io/BufferedReader;
|
||||||
|
|
||||||
|
invoke-direct {v3, v2}, Ljava/io/BufferedReader;-><init>(Ljava/io/Reader;)V
|
||||||
|
|
||||||
|
new-instance v4, Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-direct {v4}, Ljava/lang/StringBuilder;-><init>()V
|
||||||
|
|
||||||
|
:read_loop
|
||||||
|
invoke-virtual {v3}, Ljava/io/BufferedReader;->readLine()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v5
|
||||||
|
|
||||||
|
if-eqz v5, :read_done
|
||||||
|
|
||||||
|
invoke-virtual {v4, v5}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
const/16 v5, 0xa
|
||||||
|
|
||||||
|
invoke-virtual {v4, v5}, Ljava/lang/StringBuilder;->append(C)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
goto :read_loop
|
||||||
|
|
||||||
|
:read_done
|
||||||
|
invoke-virtual {v3}, Ljava/io/BufferedReader;->close()V
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Ljava/net/HttpURLConnection;->disconnect()V
|
||||||
|
|
||||||
|
invoke-virtual {v4}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v6
|
||||||
|
|
||||||
|
return-object v6
|
||||||
|
|
||||||
|
:try_end
|
||||||
|
.catchall {:try_start .. :try_end} :catch_all
|
||||||
|
|
||||||
|
:catch_all
|
||||||
|
move-exception v0
|
||||||
|
|
||||||
|
new-instance v1, Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
const-string v2, "fetch exception url="
|
||||||
|
|
||||||
|
invoke-direct {v1, v2}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
invoke-virtual {v1, p0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
const-string v2, " err="
|
||||||
|
|
||||||
|
invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Ljava/lang/Throwable;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
invoke-virtual {v1, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v1
|
||||||
|
|
||||||
|
invoke-static {v1}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
const/4 v1, 0x0
|
||||||
|
|
||||||
|
return-object v1
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public static parseLines(Ljava/lang/String;)Ljava/util/ArrayList;
|
||||||
|
.locals 11
|
||||||
|
|
||||||
|
:try_start
|
||||||
|
new-instance v0, Lorg/json/JSONObject;
|
||||||
|
|
||||||
|
invoke-direct {v0, p0}, Lorg/json/JSONObject;-><init>(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
const-string v1, "type"
|
||||||
|
|
||||||
|
const-string v2, ""
|
||||||
|
|
||||||
|
invoke-virtual {v0, v1, v2}, Lorg/json/JSONObject;->optString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v1
|
||||||
|
|
||||||
|
const-string v2, "Line"
|
||||||
|
|
||||||
|
invoke-virtual {v2, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
|
||||||
|
|
||||||
|
move-result v2
|
||||||
|
|
||||||
|
if-nez v2, :type_ok
|
||||||
|
|
||||||
|
const-string v2, "Word"
|
||||||
|
|
||||||
|
invoke-virtual {v2, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
|
||||||
|
|
||||||
|
move-result v2
|
||||||
|
|
||||||
|
if-nez v2, :type_ok
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
return-object v0
|
||||||
|
|
||||||
|
:type_ok
|
||||||
|
const-string v1, "data"
|
||||||
|
|
||||||
|
invoke-virtual {v0, v1}, Lorg/json/JSONObject;->optJSONArray(Ljava/lang/String;)Lorg/json/JSONArray;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
if-nez v0, :data_ok
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
return-object v0
|
||||||
|
|
||||||
|
:data_ok
|
||||||
|
invoke-virtual {v0}, Lorg/json/JSONArray;->length()I
|
||||||
|
|
||||||
|
move-result v1
|
||||||
|
|
||||||
|
if-nez v1, :nonempty
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
return-object v0
|
||||||
|
|
||||||
|
:nonempty
|
||||||
|
new-instance v2, Ljava/util/ArrayList;
|
||||||
|
|
||||||
|
invoke-direct {v2, v1}, Ljava/util/ArrayList;-><init>(I)V
|
||||||
|
|
||||||
|
const/4 v3, 0x0
|
||||||
|
|
||||||
|
:loop
|
||||||
|
if-ge v3, v1, :loop_done
|
||||||
|
|
||||||
|
invoke-virtual {v0, v3}, Lorg/json/JSONArray;->getJSONObject(I)Lorg/json/JSONObject;
|
||||||
|
|
||||||
|
move-result-object v4
|
||||||
|
|
||||||
|
const-string v5, "text"
|
||||||
|
|
||||||
|
const-string v6, ""
|
||||||
|
|
||||||
|
invoke-virtual {v4, v5, v6}, Lorg/json/JSONObject;->optString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v5
|
||||||
|
|
||||||
|
const-string v6, "startTime"
|
||||||
|
|
||||||
|
const-wide/16 v7, 0x0
|
||||||
|
|
||||||
|
invoke-virtual {v4, v6, v7, v8}, Lorg/json/JSONObject;->optDouble(Ljava/lang/String;D)D
|
||||||
|
|
||||||
|
move-result-wide v7
|
||||||
|
|
||||||
|
const-wide v9, 0x408f400000000000L
|
||||||
|
|
||||||
|
mul-double/2addr v7, v9
|
||||||
|
|
||||||
|
double-to-long v7, v7
|
||||||
|
|
||||||
|
new-instance v4, Lcom/tidal/android/feature/playerscreen/ui/f;
|
||||||
|
|
||||||
|
invoke-direct {v4, v5, v7, v8}, Lcom/tidal/android/feature/playerscreen/ui/f;-><init>(Ljava/lang/String;J)V
|
||||||
|
|
||||||
|
invoke-virtual {v2, v4}, Ljava/util/ArrayList;->add(Ljava/lang/Object;)Z
|
||||||
|
|
||||||
|
add-int/lit8 v3, v3, 0x1
|
||||||
|
|
||||||
|
goto :loop
|
||||||
|
|
||||||
|
:loop_done
|
||||||
|
return-object v2
|
||||||
|
|
||||||
|
:try_end
|
||||||
|
.catchall {:try_start .. :try_end} :catch_all
|
||||||
|
|
||||||
|
:catch_all
|
||||||
|
move-exception v0
|
||||||
|
|
||||||
|
const/4 v1, 0x0
|
||||||
|
|
||||||
|
return-object v1
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method private runImpl()V
|
||||||
|
.locals 11
|
||||||
|
|
||||||
|
iget-object v0, p0, Lradiant/RLAPILyricsWorker;->title:Ljava/lang/String;
|
||||||
|
|
||||||
|
const-string v1, "UTF-8"
|
||||||
|
|
||||||
|
invoke-static {v0, v1}, Ljava/net/URLEncoder;->encode(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
iget-object v2, p0, Lradiant/RLAPILyricsWorker;->artist:Ljava/lang/String;
|
||||||
|
|
||||||
|
invoke-static {v2, v1}, Ljava/net/URLEncoder;->encode(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v1
|
||||||
|
|
||||||
|
new-instance v2, Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
const-string v3, "?title="
|
||||||
|
|
||||||
|
invoke-direct {v2, v3}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
invoke-virtual {v2, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
const-string v0, "&artist="
|
||||||
|
|
||||||
|
invoke-virtual {v2, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v2, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
const-string v0, "&platform=rl-mobile"
|
||||||
|
|
||||||
|
invoke-virtual {v2, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v2}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v2
|
||||||
|
|
||||||
|
new-instance v3, Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
const-string v4, "https://api.atomix.one/rl-api"
|
||||||
|
|
||||||
|
invoke-direct {v3, v4}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
invoke-virtual {v3, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v3
|
||||||
|
|
||||||
|
const/4 v4, 0x1
|
||||||
|
|
||||||
|
invoke-static {v3, v4}, Lradiant/RLAPILyricsWorker;->fetch(Ljava/lang/String;Z)Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v3
|
||||||
|
|
||||||
|
if-nez v3, :got_body
|
||||||
|
|
||||||
|
new-instance v3, Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
const-string v4, "https://rl-api.kineticsand.net/lyrics"
|
||||||
|
|
||||||
|
invoke-direct {v3, v4}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
invoke-virtual {v3, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v3
|
||||||
|
|
||||||
|
const/4 v4, 0x0
|
||||||
|
|
||||||
|
invoke-static {v3, v4}, Lradiant/RLAPILyricsWorker;->fetch(Ljava/lang/String;Z)Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v3
|
||||||
|
|
||||||
|
if-nez v3, :got_body
|
||||||
|
|
||||||
|
const-string v4, "both primary and fallback fetches failed"
|
||||||
|
|
||||||
|
invoke-static {v4}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
sget-boolean v4, Lradiant/StickyLyrics;->enabled:Z
|
||||||
|
|
||||||
|
if-nez v4, :no_close
|
||||||
|
|
||||||
|
iget-object v4, p0, Lradiant/RLAPILyricsWorker;->vm:Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;
|
||||||
|
|
||||||
|
iget-object v5, v4, Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;->N:Lkotlinx/coroutines/flow/MutableStateFlow;
|
||||||
|
|
||||||
|
sget-object v6, Ljava/lang/Boolean;->FALSE:Ljava/lang/Boolean;
|
||||||
|
|
||||||
|
invoke-interface {v5, v6}, Lkotlinx/coroutines/flow/MutableStateFlow;->setValue(Ljava/lang/Object;)V
|
||||||
|
|
||||||
|
:no_close
|
||||||
|
return-void
|
||||||
|
|
||||||
|
:got_body
|
||||||
|
invoke-static {v3}, Lradiant/RLAPILyricsWorker;->parseLines(Ljava/lang/String;)Ljava/util/ArrayList;
|
||||||
|
|
||||||
|
move-result-object v3
|
||||||
|
|
||||||
|
if-nez v3, :parse_ok
|
||||||
|
|
||||||
|
const-string v4, "parse returned null (bad JSON / unsupported type)"
|
||||||
|
|
||||||
|
invoke-static {v4}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
|
||||||
|
:parse_ok
|
||||||
|
invoke-virtual {v3}, Ljava/util/ArrayList;->size()I
|
||||||
|
|
||||||
|
move-result v4
|
||||||
|
|
||||||
|
if-nez v4, :nonempty
|
||||||
|
|
||||||
|
const-string v5, "parsed 0 lines"
|
||||||
|
|
||||||
|
invoke-static {v5}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
|
||||||
|
:nonempty
|
||||||
|
new-instance v5, Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
const-string v6, "parsed "
|
||||||
|
|
||||||
|
invoke-direct {v5, v6}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
invoke-virtual {v5, v4}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
const-string v6, " lines"
|
||||||
|
|
||||||
|
invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v5}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v5
|
||||||
|
|
||||||
|
invoke-static {v5}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
iget-object v4, p0, Lradiant/RLAPILyricsWorker;->key:Ljava/lang/String;
|
||||||
|
|
||||||
|
sget-object v5, Lradiant/RLAPILyricsHook;->currentKey:Ljava/lang/String;
|
||||||
|
|
||||||
|
invoke-virtual {v4, v5}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
|
||||||
|
|
||||||
|
move-result v4
|
||||||
|
|
||||||
|
if-nez v4, :key_ok
|
||||||
|
|
||||||
|
const-string v5, "race-check failed (user skipped tracks)"
|
||||||
|
|
||||||
|
invoke-static {v5}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
|
||||||
|
:key_ok
|
||||||
|
iget-object v4, p0, Lradiant/RLAPILyricsWorker;->vm:Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;
|
||||||
|
|
||||||
|
iget-object v5, v4, Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;->M:Lkotlinx/coroutines/flow/MutableStateFlow;
|
||||||
|
|
||||||
|
invoke-static {v3}, Ltn0/a;->c(Ljava/lang/Iterable;)Ltn0/b;
|
||||||
|
|
||||||
|
move-result-object v6
|
||||||
|
|
||||||
|
iget-object v7, p0, Lradiant/RLAPILyricsWorker;->lyricsId:Ljava/lang/String;
|
||||||
|
|
||||||
|
if-nez v7, :have_id
|
||||||
|
|
||||||
|
const-string v7, ""
|
||||||
|
|
||||||
|
:have_id
|
||||||
|
new-instance v8, Lcom/tidal/android/feature/playerscreen/ui/g$c;
|
||||||
|
|
||||||
|
const/4 v9, -0x1
|
||||||
|
|
||||||
|
const/4 v10, 0x0
|
||||||
|
|
||||||
|
invoke-direct {v8, v7, v6, v9, v10}, Lcom/tidal/android/feature/playerscreen/ui/g$c;-><init>(Ljava/lang/String;Ltn0/b;IZ)V
|
||||||
|
|
||||||
|
const-string v6, "publishing g$c -> J=true N=true M=g$c"
|
||||||
|
|
||||||
|
invoke-static {v6}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
const/4 v6, 0x1
|
||||||
|
|
||||||
|
sput-boolean v6, Lradiant/RLAPILyricsHook;->isRlState:Z
|
||||||
|
|
||||||
|
iget-object v6, v4, Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;->J:Lkotlinx/coroutines/flow/MutableStateFlow;
|
||||||
|
|
||||||
|
sget-object v7, Ljava/lang/Boolean;->TRUE:Ljava/lang/Boolean;
|
||||||
|
|
||||||
|
invoke-interface {v6, v7}, Lkotlinx/coroutines/flow/MutableStateFlow;->setValue(Ljava/lang/Object;)V
|
||||||
|
|
||||||
|
sget-boolean v9, Lradiant/StickyLyrics;->enabled:Z
|
||||||
|
|
||||||
|
if-eqz v9, :skip_n
|
||||||
|
|
||||||
|
iget-object v6, v4, Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;->N:Lkotlinx/coroutines/flow/MutableStateFlow;
|
||||||
|
|
||||||
|
invoke-interface {v6, v7}, Lkotlinx/coroutines/flow/MutableStateFlow;->setValue(Ljava/lang/Object;)V
|
||||||
|
|
||||||
|
:skip_n
|
||||||
|
invoke-interface {v5, v8}, Lkotlinx/coroutines/flow/MutableStateFlow;->setValue(Ljava/lang/Object;)V
|
||||||
|
|
||||||
|
:done
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
# virtual methods
|
||||||
|
.method public run()V
|
||||||
|
.locals 1
|
||||||
|
|
||||||
|
:try_start
|
||||||
|
invoke-direct {p0}, Lradiant/RLAPILyricsWorker;->runImpl()V
|
||||||
|
|
||||||
|
:try_end
|
||||||
|
.catchall {:try_start .. :try_end} :catch_all
|
||||||
|
|
||||||
|
return-void
|
||||||
|
|
||||||
|
:catch_all
|
||||||
|
move-exception v0
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
.class public final Lradiant/SeekerConsumer;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
|
||||||
|
.implements Lio/reactivex/functions/Consumer;
|
||||||
|
|
||||||
|
|
||||||
|
# direct methods
|
||||||
|
.method public constructor <init>()V
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
|
||||||
|
# virtual methods
|
||||||
|
.method public final accept(Ljava/lang/Object;)V
|
||||||
|
.locals 3
|
||||||
|
|
||||||
|
if-eqz p1, :ret # null guard
|
||||||
|
|
||||||
|
instance-of v0, p1, Ljh/b; # type guard
|
||||||
|
|
||||||
|
if-eqz v0, :ret # skip if wrong type
|
||||||
|
|
||||||
|
check-cast p1, Ljh/b; # narrow type
|
||||||
|
|
||||||
|
iget v0, p1, Ljh/b;->b:F # raw progress
|
||||||
|
|
||||||
|
invoke-static {v0}, Ljava/lang/Float;->isNaN(F)Z # nan check
|
||||||
|
|
||||||
|
move-result v1 # nan flag
|
||||||
|
|
||||||
|
if-eqz v1, :not_nan # skip clamp if finite
|
||||||
|
|
||||||
|
const/4 v0, 0x0 # default to zero
|
||||||
|
|
||||||
|
:not_nan
|
||||||
|
const/4 v1, 0x0 # lower bound
|
||||||
|
|
||||||
|
invoke-static {v0, v1}, Ljava/lang/Math;->max(FF)F # clamp low
|
||||||
|
|
||||||
|
move-result v0 # clamped low
|
||||||
|
|
||||||
|
const/high16 v2, 0x3f800000 # 1.0f upper bound
|
||||||
|
|
||||||
|
invoke-static {v0, v2}, Ljava/lang/Math;->min(FF)F # clamp high
|
||||||
|
|
||||||
|
move-result v0 # final value
|
||||||
|
|
||||||
|
sget-object v1, Lradiant/MiniSeekerLine;->progressState:Landroidx/compose/runtime/MutableFloatState; # shared state
|
||||||
|
|
||||||
|
if-eqz v1, :ret # safety
|
||||||
|
|
||||||
|
invoke-interface {v1, v0}, Landroidx/compose/runtime/MutableFloatState;->setFloatValue(F)V # publish
|
||||||
|
|
||||||
|
:ret
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
|
|
||||||
# direct methods
|
# direct methods
|
||||||
.method public static final a(ILandroidx/compose/runtime/Composer;Landroidx/compose/ui/Modifier;Ltl0/a;)V
|
.method public static final a(ILandroidx/compose/runtime/Composer;Landroidx/compose/ui/Modifier;Lyl0/a;)V
|
||||||
.locals 21
|
.locals 21
|
||||||
.annotation build Landroidx/compose/runtime/Composable;
|
.annotation build Landroidx/compose/runtime/Composable;
|
||||||
.end annotation
|
.end annotation
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
|
|
||||||
const/4 v4, 0x6
|
const/4 v4, 0x6
|
||||||
|
|
||||||
invoke-static {v2, v11, v4}, Lcom/squareup/ui/market/core/theme/w;->t(Lcom/squareup/ui/market/core/theme/k$a;Landroidx/compose/runtime/Composer;I)Lcom/squareup/ui/market/core/theme/MarketStylesheet;
|
invoke-static {v2, v11, v4}, Lcom/squareup/ui/market/core/theme/x;->t(Lcom/squareup/ui/market/core/theme/k$a;Landroidx/compose/runtime/Composer;I)Lcom/squareup/ui/market/core/theme/MarketStylesheet;
|
||||||
|
|
||||||
move-result-object v15
|
move-result-object v15
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@
|
|||||||
|
|
||||||
const/16 v18, 0x0
|
const/16 v18, 0x0
|
||||||
|
|
||||||
invoke-static/range {v15 .. v20}, Lcom/squareup/ui/market/components/MarketIconButtonKt;->P(Lcom/squareup/ui/market/core/theme/MarketStylesheet;Lcom/squareup/ui/market/core/components/properties/IconButton$Size;Lcom/squareup/ui/market/core/components/properties/IconButton$Rank;Lcom/squareup/ui/market/core/components/properties/IconButton$Variant;ILjava/lang/Object;)Ll20/v1;
|
invoke-static/range {v15 .. v20}, Lcom/squareup/ui/market/components/MarketIconButtonKt;->P(Lcom/squareup/ui/market/core/theme/MarketStylesheet;Lcom/squareup/ui/market/core/components/properties/IconButton$Size;Lcom/squareup/ui/market/core/components/properties/IconButton$Rank;Lcom/squareup/ui/market/core/components/properties/IconButton$Variant;ILjava/lang/Object;)Ln20/w1;
|
||||||
|
|
||||||
move-result-object v4
|
move-result-object v4
|
||||||
|
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
:cond_5
|
:cond_5
|
||||||
move-object v9, v4
|
move-object v9, v4
|
||||||
|
|
||||||
check-cast v9, Ll20/v1;
|
check-cast v9, Ln20/w1;
|
||||||
|
|
||||||
sget v2, Lcom/tidal/android/feature/playerscreen/ui/R$string;->lyrics:I
|
sget v2, Lcom/tidal/android/feature/playerscreen/ui/R$string;->lyrics:I
|
||||||
|
|
||||||
@@ -172,7 +172,7 @@
|
|||||||
|
|
||||||
const/4 v8, 0x0
|
const/4 v8, 0x0
|
||||||
|
|
||||||
invoke-static/range {v1 .. v13}, Lcom/squareup/ui/market/components/MarketIconButtonKt;->c(Ltl0/a;Ljava/lang/String;Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/interaction/MutableInteractionSource;ZLcom/squareup/ui/market/components/n;Ltl0/a;Ljava/lang/String;Ll20/v1;Ltl0/p;Landroidx/compose/runtime/Composer;II)V
|
invoke-static/range {v1 .. v13}, Lcom/squareup/ui/market/components/MarketIconButtonKt;->c(Lyl0/a;Ljava/lang/String;Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/interaction/MutableInteractionSource;ZLcom/squareup/ui/market/components/n;Lyl0/a;Ljava/lang/String;Ln20/w1;Lyl0/p;Landroidx/compose/runtime/Composer;II)V
|
||||||
|
|
||||||
invoke-static {}, Landroidx/compose/runtime/ComposerKt;->isTraceInProgress()Z
|
invoke-static {}, Landroidx/compose/runtime/ComposerKt;->isTraceInProgress()Z
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
.source "SourceFile"
|
.source "SourceFile"
|
||||||
|
|
||||||
# interfaces
|
# interfaces
|
||||||
.implements Ltl0/p;
|
.implements Lyl0/p;
|
||||||
|
|
||||||
|
|
||||||
# virtual methods
|
# virtual methods
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.class public final Lradiant/SpvFactory;
|
.class public final Lradiant/SpvFactory;
|
||||||
.super Ljava/lang/Object;
|
.super Ljava/lang/Object;
|
||||||
.implements Ltl0/l;
|
.implements Lyl0/l;
|
||||||
|
|
||||||
|
|
||||||
# static fields
|
# static fields
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
.class public final Lradiant/StickyLyrics;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
|
||||||
|
|
||||||
|
# static fields
|
||||||
|
.field public static volatile enabled:Z
|
||||||
|
|
||||||
|
|
||||||
|
# direct methods
|
||||||
|
.method static constructor <clinit>()V
|
||||||
|
.locals 1
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
sput-boolean v0, Lradiant/StickyLyrics;->enabled:Z
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method private constructor <init>()V
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
--- a/com/tidal/android/feature/home/ui/k.smali
|
||||||
|
+++ b/com/tidal/android/feature/home/ui/k.smali
|
||||||
|
@@ -424,7 +424,7 @@
|
||||||
|
const/4 v6, 0x0
|
||||||
|
|
||||||
|
.line 181
|
||||||
|
- move-wide v7, v8
|
||||||
|
+ const-wide/16 v7, 0x0 # transparent containerColor
|
||||||
|
|
||||||
|
.line 182
|
||||||
|
const-wide/16 v9, 0x0
|
||||||
|
--- a/com/tidal/android/feature/home/ui/p.smali
|
||||||
|
+++ b/com/tidal/android/feature/home/ui/p.smali
|
||||||
|
@@ -187,6 +187,10 @@
|
||||||
|
.line 61
|
||||||
|
.line 62
|
||||||
|
:cond_3
|
||||||
|
+ const/4 v0, 0x0 # render $changed flag
|
||||||
|
+
|
||||||
|
+ invoke-static {v4, v0}, Lradiant/HomeBackdrop;->render(Landroidx/compose/runtime/Composer;I)V # render album-art backdrop
|
||||||
|
+
|
||||||
|
invoke-interface {p1}, Landroidx/compose/foundation/layout/PaddingValues;->calculateTopPadding-D9Ej5fM()F
|
||||||
|
|
||||||
|
.line 63
|
||||||
|
--- a/com/tidal/android/feature/home/ui/composables/e.smali
|
||||||
|
+++ b/com/tidal/android/feature/home/ui/composables/e.smali
|
||||||
|
@@ -790,6 +790,8 @@
|
||||||
|
.line 104
|
||||||
|
move-result-wide v14
|
||||||
|
|
||||||
|
+ const-wide/16 v14, 0x0 # transparent topBar background
|
||||||
|
+
|
||||||
|
.line 105
|
||||||
|
const/16 v17, 0x2
|
||||||
|
|
||||||
|
@@ -850,6 +852,8 @@
|
||||||
|
.line 134
|
||||||
|
move-result-wide v14
|
||||||
|
|
||||||
|
+ const-wide/16 v14, 0x0 # transparent TopAppBar containerColor
|
||||||
|
+
|
||||||
|
.line 135
|
||||||
|
sget-object v3, Landroidx/compose/ui/graphics/Color;->Companion:Landroidx/compose/ui/graphics/Color$Companion;
|
||||||
@@ -1,26 +1,26 @@
|
|||||||
--- a/com/tidal/android/feature/playerscreen/ui/g0.smali
|
--- a/com/tidal/android/feature/playerscreen/ui/h0.smali
|
||||||
+++ b/com/tidal/android/feature/playerscreen/ui/g0.smali
|
+++ b/com/tidal/android/feature/playerscreen/ui/h0.smali
|
||||||
@@ -666,8 +666,23 @@
|
@@ -669,8 +669,23 @@
|
||||||
move-object v5, v1
|
move-object v5, v1
|
||||||
|
|
||||||
.line 288
|
.line 289
|
||||||
+ const v1, 0x52414443 # group key
|
+ const v1, 0x52414443 # group key
|
||||||
+
|
+
|
||||||
+ invoke-interface {v7, v1}, Landroidx/compose/runtime/Composer;->startReplaceGroup(I)V # open group around the cover
|
+ invoke-interface {v7, v1}, Landroidx/compose/runtime/Composer;->startReplaceGroup(I)V # open group around the cover
|
||||||
+
|
+
|
||||||
+ iget-object v1, v0, Lcom/tidal/android/feature/playerscreen/ui/g0;->b:Lcom/tidal/android/feature/playerscreen/ui/r$a; # player state
|
+ iget-object v1, v0, Lcom/tidal/android/feature/playerscreen/ui/h0;->b:Lcom/tidal/android/feature/playerscreen/ui/r$a; # player state
|
||||||
+
|
+
|
||||||
+ iget-object v1, v1, Lcom/tidal/android/feature/playerscreen/ui/r$a;->j:Lcom/tidal/android/feature/playerscreen/ui/g; # current view mode
|
+ iget-object v1, v1, Lcom/tidal/android/feature/playerscreen/ui/r$a;->j:Lcom/tidal/android/feature/playerscreen/ui/g; # current view mode
|
||||||
+
|
+
|
||||||
+ instance-of v1, v1, Lcom/tidal/android/feature/playerscreen/ui/g$a; # only render when on cover mode
|
+ instance-of v1, v1, Lcom/tidal/android/feature/playerscreen/ui/g$a; # cover mode only
|
||||||
+
|
+
|
||||||
+ if-eqz v1, :radiant_after_cover # lyrics/credits mode -> skip cover
|
+ if-eqz v1, :radiant_after_cover # lyrics/credits mode -> skip cover
|
||||||
+
|
+
|
||||||
invoke-static/range {v2 .. v9}, Lcom/tidal/android/feature/playerscreen/ui/composables/CoverPagerKt;->c(Lcom/tidal/android/feature/playerscreen/ui/d;Ltl0/l;FLandroidx/compose/ui/Modifier;ZLandroidx/compose/runtime/Composer;II)V
|
invoke-static/range {v2 .. v9}, Lcom/tidal/android/feature/playerscreen/ui/composables/CoverPagerKt;->c(Lcom/tidal/android/feature/playerscreen/ui/d;Lyl0/l;FLandroidx/compose/ui/Modifier;ZLandroidx/compose/runtime/Composer;II)V
|
||||||
|
|
||||||
+ :radiant_after_cover
|
+ :radiant_after_cover # skip target
|
||||||
+ invoke-interface {v7}, Landroidx/compose/runtime/Composer;->endReplaceGroup()V # close group
|
+ invoke-interface {v7}, Landroidx/compose/runtime/Composer;->endReplaceGroup()V # close group
|
||||||
+
|
+
|
||||||
.line 289
|
|
||||||
.line 290
|
.line 290
|
||||||
.line 291
|
.line 291
|
||||||
|
.line 292
|
||||||
|
|||||||
@@ -1,11 +1,37 @@
|
|||||||
--- a/com/tidal/android/feature/playerscreen/ui/composables/k1.smali
|
--- a/com/tidal/android/feature/playerscreen/ui/composables/o1.smali
|
||||||
+++ b/com/tidal/android/feature/playerscreen/ui/composables/k1.smali
|
+++ b/com/tidal/android/feature/playerscreen/ui/composables/o1.smali
|
||||||
@@ -64,7 +64,7 @@
|
@@ -64,26 +64,29 @@
|
||||||
|
|
||||||
.line 16
|
.line 16
|
||||||
.line 17
|
.line 17
|
||||||
- iget v2, p0, Lcom/tidal/android/feature/playerscreen/ui/composables/k1;->a:F
|
- iget v2, p0, Lcom/tidal/android/feature/playerscreen/ui/composables/o1;->a:F
|
||||||
+ const/high16 v2, 0x43480000 # hardcode top fade region to 200dp (decouple from contentPadding)
|
+ const/high16 v2, 0x43200000 # 160f start dp
|
||||||
|
|
||||||
.line 18
|
.line 18
|
||||||
.line 19
|
.line 19
|
||||||
|
invoke-interface {p1, v2}, Landroidx/compose/ui/unit/Density;->toPx-0680j_4(F)F
|
||||||
|
|
||||||
|
.line 20
|
||||||
|
.line 21
|
||||||
|
.line 22
|
||||||
|
- move-result v3
|
||||||
|
+ move-result v2 # startY in px
|
||||||
|
+
|
||||||
|
+ const/high16 v3, 0x435c0000 # 220f end dp
|
||||||
|
+
|
||||||
|
+ invoke-interface {p1, v3}, Landroidx/compose/ui/unit/Density;->toPx-0680j_4(F)F # dp to px
|
||||||
|
+
|
||||||
|
+ move-result v3 # endY in px
|
||||||
|
|
||||||
|
.line 23
|
||||||
|
const/16 v5, 0x8
|
||||||
|
|
||||||
|
.line 24
|
||||||
|
.line 25
|
||||||
|
const/4 v6, 0x0
|
||||||
|
|
||||||
|
- .line 26
|
||||||
|
- const/4 v2, 0x0
|
||||||
|
-
|
||||||
|
.line 27
|
||||||
|
const/4 v4, 0x0
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
--- a/com/tidal/android/feature/playerscreen/ui/PlayerScreenKt$PlayerScreenPortrait$2$1.smali
|
||||||
|
+++ b/com/tidal/android/feature/playerscreen/ui/PlayerScreenKt$PlayerScreenPortrait$2$1.smali
|
||||||
|
@@ -228,11 +228,11 @@
|
||||||
|
.line 23
|
||||||
|
.line 24
|
||||||
|
iget-boolean p1, p0, Lcom/tidal/android/feature/playerscreen/ui/PlayerScreenKt$PlayerScreenPortrait$2$1;->$isLyricsVisible:Z
|
||||||
|
|
||||||
|
.line 25
|
||||||
|
.line 26
|
||||||
|
- if-eqz p1, :cond_3
|
||||||
|
+ goto :cond_3 # skip auto-hide branch
|
||||||
|
|
||||||
|
.line 27
|
||||||
|
.line 28
|
||||||
|
iput v2, p0, Lcom/tidal/android/feature/playerscreen/ui/PlayerScreenKt$PlayerScreenPortrait$2$1;->label:I
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
--- a/com/tidal/android/feature/playerscreen/ui/b0.smali
|
--- a/com/tidal/android/feature/playerscreen/ui/c0.smali
|
||||||
+++ b/com/tidal/android/feature/playerscreen/ui/b0.smali
|
+++ b/com/tidal/android/feature/playerscreen/ui/c0.smali
|
||||||
@@ -45,7 +45,7 @@
|
@@ -47,7 +47,7 @@
|
||||||
|
|
||||||
# virtual methods
|
# virtual methods
|
||||||
.method public final invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
|
.method public final invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
|
||||||
@@ -9,9 +9,9 @@
|
|||||||
|
|
||||||
.line 1
|
.line 1
|
||||||
move-object/from16 v0, p0
|
move-object/from16 v0, p0
|
||||||
@@ -460,6 +460,64 @@
|
@@ -461,6 +461,64 @@
|
||||||
.line 200
|
.line 200
|
||||||
invoke-static/range {v2 .. v9}, Lcom/tidal/android/feature/playerscreen/ui/composables/LyricsKt;->a(Lcom/tidal/android/feature/playerscreen/ui/g;Ltl0/l;Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/layout/PaddingValues;Ltl0/l;Landroidx/compose/runtime/Composer;II)V
|
invoke-static/range {v2 .. v9}, Lcom/tidal/android/feature/playerscreen/ui/composables/LyricsKt;->a(Lcom/tidal/android/feature/playerscreen/ui/g;Lyl0/l;Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/layout/PaddingValues;Lyl0/l;Landroidx/compose/runtime/Composer;II)V
|
||||||
|
|
||||||
+ sget-object v17, Lradiant/SpvFactory;->a:Lradiant/SpvFactory; # progress view factory
|
+ sget-object v17, Lradiant/SpvFactory;->a:Lradiant/SpvFactory; # progress view factory
|
||||||
+
|
+
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
+
|
+
|
||||||
+ const/16 v24, 0x0 # synthetic null
|
+ const/16 v24, 0x0 # synthetic null
|
||||||
+
|
+
|
||||||
+ invoke-static/range {v18 .. v24}, Landroidx/compose/foundation/ClickableKt;->clickable-XHw0xAI$default(Landroidx/compose/ui/Modifier;ZLjava/lang/String;Landroidx/compose/ui/semantics/Role;Ltl0/a;ILjava/lang/Object;)Landroidx/compose/ui/Modifier; # apply clickable
|
+ invoke-static/range {v18 .. v24}, Landroidx/compose/foundation/ClickableKt;->clickable-XHw0xAI$default(Landroidx/compose/ui/Modifier;ZLjava/lang/String;Landroidx/compose/ui/semantics/Role;Lyl0/a;ILjava/lang/Object;)Landroidx/compose/ui/Modifier; # apply clickable
|
||||||
+
|
+
|
||||||
+ move-result-object v23 # clickable modifier
|
+ move-result-object v23 # clickable modifier
|
||||||
+
|
+
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
+
|
+
|
||||||
+ const/16 v22, 0x4 # default mask
|
+ const/16 v22, 0x4 # default mask
|
||||||
+
|
+
|
||||||
+ invoke-static/range {v17 .. v22}, Landroidx/compose/ui/viewinterop/AndroidView_androidKt;->AndroidView(Ltl0/l;Landroidx/compose/ui/Modifier;Ltl0/l;Landroidx/compose/runtime/Composer;II)V # mount progress pill
|
+ invoke-static/range {v17 .. v22}, Landroidx/compose/ui/viewinterop/AndroidView_androidKt;->AndroidView(Lyl0/l;Landroidx/compose/ui/Modifier;Lyl0/l;Landroidx/compose/runtime/Composer;II)V # mount progress pill
|
||||||
+
|
+
|
||||||
.line 201
|
.line 201
|
||||||
.line 202
|
.line 202
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
# rl-locals: com/tidal/android/feature/playerscreen/ui/PlayerScreenKt.smali e( 79
|
# rl-locals: com/tidal/android/feature/playerscreen/ui/PlayerScreenKt.smali e( 79
|
||||||
--- a/com/tidal/android/feature/playerscreen/ui/PlayerScreenKt.smali
|
--- a/com/tidal/android/feature/playerscreen/ui/PlayerScreenKt.smali
|
||||||
+++ b/com/tidal/android/feature/playerscreen/ui/PlayerScreenKt.smali
|
+++ b/com/tidal/android/feature/playerscreen/ui/PlayerScreenKt.smali
|
||||||
@@ -4931,7 +4931,11 @@
|
@@ -4945,7 +4945,11 @@
|
||||||
const/4 v10, 0x0
|
const/4 v7, 0x0
|
||||||
|
|
||||||
.line 226
|
.line 226
|
||||||
- invoke-static {v10, v9, v4, v2, v7}, Lcom/tidal/android/feature/playerscreen/ui/composables/h1;->a(Landroidx/compose/ui/Modifier;Ltl0/a;ZLandroidx/compose/runtime/Composer;I)V
|
- invoke-static {v7, v9, v4, v2, v10}, Lcom/tidal/android/feature/playerscreen/ui/composables/k1;->a(Landroidx/compose/ui/Modifier;Lyl0/a;ZLandroidx/compose/runtime/Composer;I)V
|
||||||
+ const v10, 0x52414448 # empty group key
|
+ const v10, 0x52414448 # empty group key
|
||||||
+
|
+
|
||||||
+ invoke-interface {v2, v10}, Landroidx/compose/runtime/Composer;->startReplaceGroup(I)V # open empty
|
+ invoke-interface {v2, v10}, Landroidx/compose/runtime/Composer;->startReplaceGroup(I)V # open empty
|
||||||
@@ -14,21 +14,21 @@
|
|||||||
|
|
||||||
.line 227
|
.line 227
|
||||||
invoke-interface {v2}, Landroidx/compose/runtime/Composer;->endReplaceGroup()V
|
invoke-interface {v2}, Landroidx/compose/runtime/Composer;->endReplaceGroup()V
|
||||||
@@ -5838,6 +5838,22 @@
|
@@ -5720,6 +5724,22 @@
|
||||||
:cond_51
|
:cond_51
|
||||||
check-cast v4, Ltl0/a;
|
check-cast v4, Lyl0/a;
|
||||||
|
|
||||||
const/4 v2, 0x0
|
const/4 v2, 0x0
|
||||||
|
|
||||||
invoke-static {v13, v7, v2, v4}, Lcom/tidal/android/feature/playerscreen/ui/composables/h3;->a(ILandroidx/compose/runtime/Composer;Landroidx/compose/ui/Modifier;Ltl0/a;)V
|
invoke-static {v13, v7, v2, v4}, Lcom/tidal/android/feature/playerscreen/ui/composables/l3;->a(ILandroidx/compose/runtime/Composer;Landroidx/compose/ui/Modifier;Lyl0/a;)V
|
||||||
+
|
+
|
||||||
+ new-instance v74, Lc8/j; # lyrics-toggle lambda
|
+ new-instance v74, Lcom/tidal/android/feature/playerscreen/ui/g0; # lyrics toggle lambda
|
||||||
+
|
+
|
||||||
+ move-object/from16 v75, p5 # lambda receiver
|
+ move-object/from16 v75, p5 # action dispatcher
|
||||||
+
|
+
|
||||||
+ const/16 v76, 0x1 # lyrics action
|
+ const/16 v76, 0x0 # lyrics action disc
|
||||||
+
|
+
|
||||||
+ invoke-direct/range {v74 .. v76}, Lc8/j;-><init>(Ljava/lang/Object;I)V # build lambda
|
+ invoke-direct/range {v74 .. v76}, Lcom/tidal/android/feature/playerscreen/ui/g0;-><init>(Ljava/lang/Object;I)V # build lambda
|
||||||
+
|
+
|
||||||
+ const/16 v71, 0x0 # changed flags
|
+ const/16 v71, 0x0 # changed flags
|
||||||
+
|
+
|
||||||
@@ -36,4 +36,4 @@
|
|||||||
+
|
+
|
||||||
+ const/16 v73, 0x0 # null modifier
|
+ const/16 v73, 0x0 # null modifier
|
||||||
+
|
+
|
||||||
+ invoke-static/range {v71 .. v74}, Lradiant/SparkleButton;->a(ILandroidx/compose/runtime/Composer;Landroidx/compose/ui/Modifier;Ltl0/a;)V # render sparkle button
|
+ invoke-static/range {v71 .. v74}, Lradiant/SparkleButton;->a(ILandroidx/compose/runtime/Composer;Landroidx/compose/ui/Modifier;Lyl0/a;)V # render sparkle button
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
--- a/com/tidal/android/feature/playerscreen/ui/PlayerScreenKt.smali
|
--- a/com/tidal/android/feature/playerscreen/ui/PlayerScreenKt.smali
|
||||||
+++ b/com/tidal/android/feature/playerscreen/ui/PlayerScreenKt.smali
|
+++ b/com/tidal/android/feature/playerscreen/ui/PlayerScreenKt.smali
|
||||||
@@ -5086,7 +5086,11 @@
|
@@ -5094,7 +5094,11 @@
|
||||||
const/4 v13, 0x0
|
const/4 v13, 0x0
|
||||||
|
|
||||||
.line 247
|
.line 247
|
||||||
- invoke-static {v9, v0, v13, v2, v10}, Lcom/tidal/android/feature/playerscreen/ui/composables/BroadcastButtonKt;->b(Lcom/tidal/android/feature/playerscreen/ui/b;Ltl0/a;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;I)V
|
- invoke-static {v9, v0, v10, v2, v13}, Lcom/tidal/android/feature/playerscreen/ui/composables/BroadcastButtonKt;->b(Lcom/tidal/android/feature/playerscreen/ui/b;Lyl0/a;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;I)V
|
||||||
+ const v9, 0x52414244 # empty group key
|
+ const v9, 0x52414244 # empty group key
|
||||||
+
|
+
|
||||||
+ invoke-interface {v2, v9}, Landroidx/compose/runtime/Composer;->startReplaceGroup(I)V # open empty
|
+ invoke-interface {v2, v9}, Landroidx/compose/runtime/Composer;->startReplaceGroup(I)V # open empty
|
||||||
@@ -13,39 +13,38 @@
|
|||||||
|
|
||||||
.line 248
|
.line 248
|
||||||
invoke-interface {v2}, Landroidx/compose/runtime/Composer;->endNode()V
|
invoke-interface {v2}, Landroidx/compose/runtime/Composer;->endNode()V
|
||||||
@@ -5758,17 +5758,19 @@
|
@@ -5770,16 +5774,20 @@
|
||||||
|
.line 335
|
||||||
:cond_53
|
:cond_53
|
||||||
- new-instance v4, Landroidx/compose/foundation/text/input/internal/selection/l;
|
- new-instance v4, Lcom/aspiro/wamp/tidalconnect/playback/i;
|
||||||
-
|
-
|
||||||
- const/4 v6, 0x2
|
- invoke-direct {v4, v11, v1}, Lcom/aspiro/wamp/tidalconnect/playback/i;-><init>(Ljava/lang/Object;I)V
|
||||||
-
|
|
||||||
- invoke-direct {v4, v11, v6}, Landroidx/compose/foundation/text/input/internal/selection/l;-><init>(Ljava/lang/Object;I)V
|
|
||||||
-
|
-
|
||||||
- .line 336
|
- .line 336
|
||||||
- invoke-interface {v7, v4}, Landroidx/compose/runtime/Composer;->updateRememberedValue(Ljava/lang/Object;)V
|
- invoke-interface {v7, v4}, Landroidx/compose/runtime/Composer;->updateRememberedValue(Ljava/lang/Object;)V
|
||||||
-
|
-
|
||||||
- .line 337
|
- .line 337
|
||||||
- :cond_54
|
- :cond_54
|
||||||
- check-cast v4, Ltl0/a;
|
- check-cast v4, Lyl0/a;
|
||||||
-
|
-
|
||||||
- const/4 v2, 0x0
|
- const/4 v2, 0x0
|
||||||
-
|
-
|
||||||
- invoke-static {v13, v7, v2, v4}, Lcom/tidal/android/feature/playerscreen/ui/composables/u4;->a(ILandroidx/compose/runtime/Composer;Landroidx/compose/ui/Modifier;Ltl0/a;)V
|
- invoke-static {v13, v7, v2, v4}, Lcom/tidal/android/feature/playerscreen/ui/composables/x4;->a(ILandroidx/compose/runtime/Composer;Landroidx/compose/ui/Modifier;Lyl0/a;)V
|
||||||
+ new-instance v4, Lcom/tidal/android/feature/playerscreen/ui/f0; # connect click lambda
|
+ new-instance v4, Landroidx/navigation/fragment/k; # connect click lambda factory
|
||||||
+
|
+
|
||||||
+ move-object/from16 v6, p5 # action dispatcher
|
+ move-object/from16 v6, p5 # action dispatcher
|
||||||
+
|
+
|
||||||
+ const/4 v8, 0x0 # connect-clicked discriminator
|
+ const/4 v8, 0x1 # connect-clicked disc
|
||||||
+
|
+
|
||||||
+ invoke-direct {v4, v6, v8}, Lcom/tidal/android/feature/playerscreen/ui/f0;-><init>(Ljava/lang/Object;I)V # build lambda
|
+ invoke-direct {v4, v6, v8}, Landroidx/navigation/fragment/k;-><init>(Ljava/lang/Object;I)V # build lambda
|
||||||
+
|
+
|
||||||
+ invoke-interface {v7, v4}, Landroidx/compose/runtime/Composer;->updateRememberedValue(Ljava/lang/Object;)V # cache lambda
|
+ invoke-interface {v7, v4}, Landroidx/compose/runtime/Composer;->updateRememberedValue(Ljava/lang/Object;)V # cache lambda
|
||||||
+
|
+
|
||||||
+ :cond_54
|
+ :cond_54 # cache join
|
||||||
+ check-cast v4, Ltl0/a;
|
+ check-cast v4, Lyl0/a; # cast to Function0
|
||||||
+
|
+
|
||||||
+ iget-object v8, v10, Lcom/tidal/android/feature/playerscreen/ui/r$a;->d:Lcom/tidal/android/feature/playerscreen/ui/b; # broadcast state
|
+ iget-object v8, v10, Lcom/tidal/android/feature/playerscreen/ui/r$a;->d:Lcom/tidal/android/feature/playerscreen/ui/b; # broadcast state
|
||||||
+
|
+
|
||||||
+ const/4 v2, 0x0 # changed flags
|
+ const/4 v2, 0x0 # changed flags
|
||||||
+
|
+
|
||||||
+ invoke-static {v8, v4, v13, v7, v2}, Lcom/tidal/android/feature/playerscreen/ui/composables/BroadcastButtonKt;->b(Lcom/tidal/android/feature/playerscreen/ui/b;Ltl0/a;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;I)V # render connect button
|
+ invoke-static {v8, v4, v13, v7, v2}, Lcom/tidal/android/feature/playerscreen/ui/composables/BroadcastButtonKt;->b(Lcom/tidal/android/feature/playerscreen/ui/b;Lyl0/a;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;I)V # render connect button
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
--- a/com/tidal/android/feature/playerscreen/ui/PlayerViewModel$observeLyricsProgress$1$a.smali
|
||||||
|
+++ b/com/tidal/android/feature/playerscreen/ui/PlayerViewModel$observeLyricsProgress$1$a.smali
|
||||||
|
@@ -190,7 +190,14 @@
|
||||||
|
goto :goto_0
|
||||||
|
|
||||||
|
.line 67
|
||||||
|
:cond_3
|
||||||
|
+ sget-boolean v6, Lradiant/RLAPILyricsHook;->isRlState:Z # read RL flag
|
||||||
|
+
|
||||||
|
+ if-eqz v6, :radiant_skip # skip if not RL
|
||||||
|
+
|
||||||
|
+ const/4 v5, -0x1 # RL no-match default = -1
|
||||||
|
+
|
||||||
|
+ :radiant_skip
|
||||||
|
if-gez v4, :cond_4
|
||||||
|
|
||||||
|
.line 68
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
--- a/com/tidal/android/feature/playerscreen/ui/PlayerViewModel.smali
|
||||||
|
+++ b/com/tidal/android/feature/playerscreen/ui/PlayerViewModel.smali
|
||||||
|
@@ -1277,7 +1277,9 @@
|
||||||
|
.line 104
|
||||||
|
.line 105
|
||||||
|
check-cast v1, Lcom/aspiro/wamp/model/Track;
|
||||||
|
|
||||||
|
+ invoke-static {v0, v1}, Lradiant/RLAPILyricsHook;->onWampTrack(Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;Lcom/aspiro/wamp/model/Track;)V # RL API lyrics hook
|
||||||
|
+
|
||||||
|
.line 106
|
||||||
|
.line 107
|
||||||
|
invoke-virtual {v1}, Lcom/aspiro/wamp/model/MediaItem;->getId()I
|
||||||
|
@@ -1551,7 +1553,7 @@
|
||||||
|
.line 235
|
||||||
|
.line 236
|
||||||
|
.line 237
|
||||||
|
- if-nez v9, :cond_a
|
||||||
|
+ goto :cond_a # skip TIDAL N=false setter
|
||||||
|
|
||||||
|
.line 238
|
||||||
|
.line 239
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
--- a/com/tidal/android/feature/playerscreen/ui/PlayerScreenKt.smali
|
--- a/com/tidal/android/feature/playerscreen/ui/PlayerScreenKt.smali
|
||||||
+++ b/com/tidal/android/feature/playerscreen/ui/PlayerScreenKt.smali
|
+++ b/com/tidal/android/feature/playerscreen/ui/PlayerScreenKt.smali
|
||||||
@@ -5854,17 +5854,38 @@
|
@@ -5725,17 +5725,38 @@
|
||||||
invoke-static {v13, v7, v2, v4}, Lcom/tidal/android/feature/playerscreen/ui/composables/h3;->a(ILandroidx/compose/runtime/Composer;Landroidx/compose/ui/Modifier;Ltl0/a;)V
|
invoke-static {v13, v7, v2, v4}, Lcom/tidal/android/feature/playerscreen/ui/composables/l3;->a(ILandroidx/compose/runtime/Composer;Landroidx/compose/ui/Modifier;Lyl0/a;)V
|
||||||
+
|
+
|
||||||
+ iget-boolean v2, v10, Lcom/tidal/android/feature/playerscreen/ui/r$a;->i:Z # hasLyrics flag
|
+ iget-boolean v2, v10, Lcom/tidal/android/feature/playerscreen/ui/r$a;->i:Z # hasLyrics flag
|
||||||
+
|
+
|
||||||
@@ -11,13 +11,13 @@
|
|||||||
+
|
+
|
||||||
+ invoke-interface {v7, v2}, Landroidx/compose/runtime/Composer;->startReplaceGroup(I)V # open lyrics branch
|
+ invoke-interface {v7, v2}, Landroidx/compose/runtime/Composer;->startReplaceGroup(I)V # open lyrics branch
|
||||||
|
|
||||||
new-instance v74, Lc8/j; # lyrics-toggle lambda
|
new-instance v74, Lcom/tidal/android/feature/playerscreen/ui/g0; # lyrics toggle lambda
|
||||||
|
|
||||||
move-object/from16 v75, p5 # lambda receiver
|
move-object/from16 v75, p5 # action dispatcher
|
||||||
|
|
||||||
const/16 v76, 0x1 # lyrics action
|
const/16 v76, 0x0 # lyrics action disc
|
||||||
|
|
||||||
invoke-direct/range {v74 .. v76}, Lc8/j;-><init>(Ljava/lang/Object;I)V # build lambda
|
invoke-direct/range {v74 .. v76}, Lcom/tidal/android/feature/playerscreen/ui/g0;-><init>(Ljava/lang/Object;I)V # build lambda
|
||||||
|
|
||||||
const/16 v71, 0x0 # changed flags
|
const/16 v71, 0x0 # changed flags
|
||||||
|
|
||||||
@@ -25,17 +25,17 @@
|
|||||||
|
|
||||||
const/16 v73, 0x0 # null modifier
|
const/16 v73, 0x0 # null modifier
|
||||||
|
|
||||||
invoke-static/range {v71 .. v74}, Lradiant/SparkleButton;->a(ILandroidx/compose/runtime/Composer;Landroidx/compose/ui/Modifier;Ltl0/a;)V # render sparkle button
|
invoke-static/range {v71 .. v74}, Lradiant/SparkleButton;->a(ILandroidx/compose/runtime/Composer;Landroidx/compose/ui/Modifier;Lyl0/a;)V # render sparkle button
|
||||||
+
|
+
|
||||||
+ invoke-interface {v7}, Landroidx/compose/runtime/Composer;->endReplaceGroup()V # close lyrics branch
|
+ invoke-interface {v7}, Landroidx/compose/runtime/Composer;->endReplaceGroup()V # close lyrics branch
|
||||||
+
|
+
|
||||||
+ goto :goto_sparkle_done # skip empty branch
|
+ goto :goto_sparkle_done # skip empty branch
|
||||||
+
|
+
|
||||||
+ :cond_sparkle_no_lyrics
|
+ :cond_sparkle_no_lyrics # no-lyrics branch
|
||||||
+ const v2, 0x3057f75c # group key (no lyrics)
|
+ const v2, 0x3057f75c # group key (no lyrics)
|
||||||
+
|
+
|
||||||
+ invoke-interface {v7, v2}, Landroidx/compose/runtime/Composer;->startReplaceGroup(I)V # open empty
|
+ invoke-interface {v7, v2}, Landroidx/compose/runtime/Composer;->startReplaceGroup(I)V # open empty
|
||||||
+
|
+
|
||||||
+ invoke-interface {v7}, Landroidx/compose/runtime/Composer;->endReplaceGroup()V # close empty
|
+ invoke-interface {v7}, Landroidx/compose/runtime/Composer;->endReplaceGroup()V # close empty
|
||||||
+
|
+
|
||||||
+ :goto_sparkle_done
|
+ :goto_sparkle_done # join target
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
--- a/com/tidal/android/feature/playerscreen/ui/PlayerViewModel.smali
|
||||||
|
+++ b/com/tidal/android/feature/playerscreen/ui/PlayerViewModel.smali
|
||||||
|
@@ -1060,9 +1060,13 @@
|
||||||
|
move-object/from16 v0, p0
|
||||||
|
|
||||||
|
.line 2
|
||||||
|
.line 3
|
||||||
|
move-object/from16 v1, p1
|
||||||
|
|
||||||
|
+ const/4 v2, 0x1 # true literal
|
||||||
|
+
|
||||||
|
+ sput-boolean v2, Lradiant/StickyLyrics;->enabled:Z # arm sticky flag
|
||||||
|
+
|
||||||
|
.line 4
|
||||||
|
.line 5
|
||||||
|
invoke-virtual {v0}, Ljava/lang/Object;->getClass()Ljava/lang/Class;
|
||||||
|
@@ -1568,9 +1572,15 @@
|
||||||
|
.line 244
|
||||||
|
.line 245
|
||||||
|
.line 246
|
||||||
|
:cond_a
|
||||||
|
+ iget-object v9, v0, Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;->N:Lkotlinx/coroutines/flow/MutableStateFlow; # N flow
|
||||||
|
+
|
||||||
|
+ sget-object v10, Ljava/lang/Boolean;->TRUE:Ljava/lang/Boolean; # TRUE literal
|
||||||
|
+
|
||||||
|
+ invoke-interface {v9, v10}, Lkotlinx/coroutines/flow/MutableStateFlow;->setValue(Ljava/lang/Object;)V # force N=true
|
||||||
|
+
|
||||||
|
iget-object v9, v0, Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;->K:Lkotlinx/coroutines/flow/MutableStateFlow;
|
||||||
|
|
||||||
|
.line 247
|
||||||
|
.line 248
|
||||||
|
iget-object v10, v1, Lcom/tidal/android/tidalapi/domain/model/o;->j:Lcom/tidal/android/tidalapi/domain/model/a;
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"patches": [
|
||||||
|
{
|
||||||
|
"id": "EnableLegacyUi",
|
||||||
|
"order": 10,
|
||||||
|
"fileNames": ["enable-legacy-ui.patch"],
|
||||||
|
"title": "Enable Legacy UI",
|
||||||
|
"description": "[This patch will stop working soon] - Replaces the New Compose based UI with the Legacy UI (disables player-market-ui feature flag)",
|
||||||
|
"default": false,
|
||||||
|
"requires": ["DebugMenuUnlock"],
|
||||||
|
"disables": [
|
||||||
|
"LyricsDisableCover",
|
||||||
|
"LyricsReplaceLyricsButton",
|
||||||
|
"LyricsReplaceShareButton",
|
||||||
|
"LyricsRlApi",
|
||||||
|
"LyricsKeepControlsVisible",
|
||||||
|
"PlayerBackdrop",
|
||||||
|
"QualityBadgeColors",
|
||||||
|
"LyricsProgressPill",
|
||||||
|
"CoverEverywhere",
|
||||||
|
"MiniPlayerRedesign"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "LyricsRlApi",
|
||||||
|
"order": 20,
|
||||||
|
"fileNames": ["lyrics-rl-api.patch", "lyrics-rl-api-observer.patch"],
|
||||||
|
"title": "Radiant Lyrics API - WIP",
|
||||||
|
"description": "Use Radiant Lyrics API to fetch Lyrics (Higher quality & More providers)",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "PlayerBackdrop",
|
||||||
|
"order": 30,
|
||||||
|
"fileNames": ["player-backdrop.patch"],
|
||||||
|
"title": "Player Backdrop",
|
||||||
|
"description": "Restores the legacy translucent backdrop blur behind the player",
|
||||||
|
"default": true,
|
||||||
|
"advancedOptions": [
|
||||||
|
{
|
||||||
|
"type": "slider",
|
||||||
|
"key": "blur_strength",
|
||||||
|
"title": "Blur Strength",
|
||||||
|
"description": "How strongly the album art behind the player is blurred",
|
||||||
|
"default": 50,
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"steps": 19,
|
||||||
|
"displayAsPercent": true,
|
||||||
|
"token": "RL_BLUR_BITS",
|
||||||
|
"encode": { "kind": "floatBits", "scale": 1.8 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "slider",
|
||||||
|
"key": "dimming",
|
||||||
|
"title": "Backdrop Dimming",
|
||||||
|
"description": "Darkens the backdrop so the foreground controls stay legible",
|
||||||
|
"default": 50,
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"steps": 19,
|
||||||
|
"displayAsPercent": true,
|
||||||
|
"token": "RL_SCRIM_ARGB",
|
||||||
|
"encode": { "kind": "argbAlpha" }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "CoverEverywhere",
|
||||||
|
"order": 35,
|
||||||
|
"fileNames": ["home-backdrop.patch", "collection-backdrop.patch", "cover-capture.patch"],
|
||||||
|
"title": "Cover Everywhere - WIP",
|
||||||
|
"description": "Applies the blurred backdrop to every Compose based page (Home & Collection)",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "QualityBadgeColors",
|
||||||
|
"order": 36,
|
||||||
|
"fileNames": ["player-quality-badge-colors.patch"],
|
||||||
|
"title": "Quality Badge Colors",
|
||||||
|
"description": "Brings color back to the player quality badge.",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "PlayerOneHanded",
|
||||||
|
"order": 37,
|
||||||
|
"fileNames": ["player-one-handed.patch"],
|
||||||
|
"title": "One-Handed Mode",
|
||||||
|
"description": "Mirrors the bottom player buttons left-to-right for easier one-handed reach.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "LyricsProgressPill",
|
||||||
|
"order": 40,
|
||||||
|
"fileNames": ["lyrics-progress-pill.patch", "lyrics-fade-region.patch"],
|
||||||
|
"title": "Lyrics Progress Pill",
|
||||||
|
"description": "Restores the old Track Progress Pill in the top of the Lyrics screen!",
|
||||||
|
"default": true,
|
||||||
|
"requires": ["LyricsDisableCover", "LyricsReplaceLyricsButton", "LyricsReplaceShareButton"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "LyricsDisableCover",
|
||||||
|
"order": 41,
|
||||||
|
"fileNames": ["lyrics-disable-cover.patch"],
|
||||||
|
"title": "Disable Lyrics Cover",
|
||||||
|
"description": "Removes the mini track cover from the lyrics screen.",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "LyricsReplaceLyricsButton",
|
||||||
|
"order": 42,
|
||||||
|
"fileNames": ["lyrics-replace-lyrics-button.patch", "lyrics-sparkle-conditional-visibility.patch"],
|
||||||
|
"title": "Replace Lyrics Button",
|
||||||
|
"description": "Replaces the Lyrics button with the RL Sparkle!",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "LyricsReplaceShareButton",
|
||||||
|
"order": 43,
|
||||||
|
"fileNames": ["lyrics-replace-share-button.patch"],
|
||||||
|
"title": "Replace Share Button",
|
||||||
|
"description": "Replaces the share button with the Tidal Connect button from the top of the screen. (it's in the menu already)",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "MiniPlayerRedesign",
|
||||||
|
"order": 50,
|
||||||
|
"fileNames": [],
|
||||||
|
"title": "Redesigned Mini-Player",
|
||||||
|
"description": "Integrates the Seekbar into the control panel at the bottom of the screen.",
|
||||||
|
"default": false,
|
||||||
|
"defaultVariantIndex": 2,
|
||||||
|
"variants": [
|
||||||
|
{ "title": "Floating", "fileNames": [] },
|
||||||
|
{ "title": "Grey", "fileNames": ["mini-player-grey.patch"] },
|
||||||
|
{ "title": "Black", "fileNames": ["mini-player-black.patch"] }
|
||||||
|
],
|
||||||
|
"advancedOptions": [
|
||||||
|
{
|
||||||
|
"type": "toggle",
|
||||||
|
"key": "dynamic_bg",
|
||||||
|
"title": "Mini-Player Dynamic Background",
|
||||||
|
"description": "Colorizes the Mini-Player depending on the currently playing track.",
|
||||||
|
"default": false,
|
||||||
|
"inline": true,
|
||||||
|
"fileNames": ["mini-player-dynamic-bg.patch"],
|
||||||
|
"extensionFiles": ["radiant/MiniPlayerBackground.smali"],
|
||||||
|
"hidesVariants": [2],
|
||||||
|
"relabelVariants": { "1": "Legacy" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "toggle",
|
||||||
|
"key": "border",
|
||||||
|
"title": "Progress Border",
|
||||||
|
"description": "Draws an animated progress ring around the floating mini-player. Requires the Floating style.",
|
||||||
|
"default": false,
|
||||||
|
"requiresVariant": 0,
|
||||||
|
"fileNames": ["mini-player-floating-border.patch"],
|
||||||
|
"extensionFiles": ["radiant/MiniSeekerFloating.smali"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "toggle",
|
||||||
|
"key": "gestures",
|
||||||
|
"title": "Mini-Player Gestures",
|
||||||
|
"description": "Adds swipe up gesture to the mini-player to reopen the player",
|
||||||
|
"default": true,
|
||||||
|
"fileNames": ["mini-player-gestures.patch"],
|
||||||
|
"extensionFiles": [
|
||||||
|
"radiant/MiniPlayerGestures.smali",
|
||||||
|
"radiant/MiniPlayerGestures$Gesture.smali",
|
||||||
|
"radiant/MiniPlayerGestures$FeedbackLayer.smali",
|
||||||
|
"radiant/MiniPlayerGestures$FeedbackResetAnimator.smali",
|
||||||
|
"radiant/MiniPlayerGestures$RootGesture.smali",
|
||||||
|
"radiant/MiniPlayerGestures$ApplyPending.smali"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "choice",
|
||||||
|
"key": "swipe_up_drag",
|
||||||
|
"title": "Swipe Up Style",
|
||||||
|
"entries": ["Static", "Drag"],
|
||||||
|
"values": ["0x0", "0x1"],
|
||||||
|
"defaultIndex": 1,
|
||||||
|
"requiresOption": "gestures",
|
||||||
|
"token": "RL_MINI_PLAYER_SWIPE_UP_DRAG"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "toggle",
|
||||||
|
"key": "next_prev",
|
||||||
|
"title": "Next/Previous Gestures",
|
||||||
|
"description": "Swipe left or right on the mini-player to skip tracks.",
|
||||||
|
"default": false,
|
||||||
|
"requiresOption": "gestures",
|
||||||
|
"fileNames": ["mini-player-gestures-left-right.patch"],
|
||||||
|
"extensionFiles": [
|
||||||
|
"radiant/MiniPlayerTrackGestures.smali",
|
||||||
|
"radiant/MiniPlayerTrackGestures$Gesture.smali",
|
||||||
|
"radiant/MiniPlayerTrackGestures$OffsetLayer.smali",
|
||||||
|
"radiant/MiniPlayerTrackGestures$ResetAnimator.smali",
|
||||||
|
"radiant/MiniPlayerTrackGestures$TextDraw.smali",
|
||||||
|
"com/tidal/android/feature/appscaffold/ui/q$c.smali"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "LyricsKeepControlsVisible",
|
||||||
|
"order": 60,
|
||||||
|
"fileNames": ["lyrics-keep-controls-visible.patch"],
|
||||||
|
"title": "Keep Controls Visible",
|
||||||
|
"description": "Inverts the auto-hide behavior on the lyrics screen, playback controls stay visible by default and only hide when you tap them off.",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "DebugMenuUnlock",
|
||||||
|
"order": 100,
|
||||||
|
"fileNames": ["debug-menu-unlock.patch"],
|
||||||
|
"title": "Unlock Debug Menu",
|
||||||
|
"description": "Reveals TIDAL's hidden Debug Menu in Settings. (Unlocks Feature Flags & the legacy Debug Options)",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
--- a/com/tidal/android/feature/appscaffold/ui/composable/a.smali
|
||||||
|
+++ b/com/tidal/android/feature/appscaffold/ui/composable/a.smali
|
||||||
|
@@ -116,7 +116,7 @@
|
||||||
|
|
||||||
|
.line 46
|
||||||
|
.line 47
|
||||||
|
- const/16 v0, 0x24
|
||||||
|
+ const/16 v0, 0x0 # zero pill corner radius
|
||||||
|
|
||||||
|
.line 48
|
||||||
|
.line 49
|
||||||
|
@@ -162,7 +162,7 @@
|
||||||
|
|
||||||
|
.line 68
|
||||||
|
.line 69
|
||||||
|
- const/16 v0, 0x8
|
||||||
|
+ const/16 v0, 0x0 # zero bottom padding
|
||||||
|
|
||||||
|
.line 70
|
||||||
|
.line 71
|
||||||
|
--- a/com/tidal/android/feature/appscaffold/ui/composable/i.smali
|
||||||
|
+++ b/com/tidal/android/feature/appscaffold/ui/composable/i.smali
|
||||||
|
@@ -2756,12 +2756,7 @@
|
||||||
|
invoke-static {v0, v8, v11, v12}, Landroidx/compose/foundation/layout/SizeKt;->fillMaxWidth$default(Landroidx/compose/ui/Modifier;FILjava/lang/Object;)Landroidx/compose/ui/Modifier;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
-
|
||||||
|
- .line 140
|
||||||
|
- invoke-static {v0}, Landroidx/compose/foundation/layout/WindowInsetsPadding_androidKt;->navigationBarsPadding(Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier;
|
||||||
|
-
|
||||||
|
- move-result-object v0
|
||||||
|
-
|
||||||
|
+ # flush bottom; skip nav inset
|
||||||
|
.line 141
|
||||||
|
invoke-interface/range {v17 .. v17}, Landroidx/compose/runtime/State;->getValue()Ljava/lang/Object;
|
||||||
|
|
||||||
|
@@ -2773,6 +2768,8 @@
|
||||||
|
|
||||||
|
move-result v10
|
||||||
|
|
||||||
|
+ const/4 v10, 0x0 # zero horizontal padding
|
||||||
|
+
|
||||||
|
const/4 v11, 0x2
|
||||||
|
|
||||||
|
.line 142
|
||||||
|
@@ -2816,6 +2813,12 @@
|
||||||
|
|
||||||
|
move-result-wide v10
|
||||||
|
|
||||||
|
+ const v8, -0x1000000 # 0xFF000000 opaque black
|
||||||
|
+
|
||||||
|
+ invoke-static {v8}, Landroidx/compose/ui/graphics/ColorKt;->Color(I)J # pack to color long
|
||||||
|
+
|
||||||
|
+ move-result-wide v10 # override theme color
|
||||||
|
+
|
||||||
|
.line 147
|
||||||
|
sget-object v8, Lcom/tidal/android/feature/appscaffold/ui/composable/a;->c:Landroidx/compose/foundation/shape/RoundedCornerShape;
|
||||||
|
|
||||||
|
@@ -2857,6 +2860,10 @@
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
+ invoke-static {v0}, Landroidx/compose/foundation/layout/WindowInsetsPadding_androidKt;->navigationBarsPadding(Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier; # inset bar content above nav bar (Prevents Bleeding behind Nav Buttons)
|
||||||
|
+
|
||||||
|
+ move-result-object v0
|
||||||
|
+
|
||||||
|
.line 155
|
||||||
|
sget-object v8, Landroidx/compose/foundation/layout/Arrangement;->INSTANCE:Landroidx/compose/foundation/layout/Arrangement;
|
||||||
|
|
||||||
|
@@ -2969,6 +2976,10 @@
|
||||||
|
|
||||||
|
invoke-static {v12, v0, v8}, Landroidx/compose/runtime/Updater;->set-impl(Landroidx/compose/runtime/Composer;Ljava/lang/Object;Lyl0/p;)V
|
||||||
|
|
||||||
|
+ const/4 v8, 0x0 # composer $changed flags
|
||||||
|
+
|
||||||
|
+ invoke-static {v15, v8}, Lradiant/MiniSeekerLine;->render(Landroidx/compose/runtime/Composer;I)V # draw top-edge seeker
|
||||||
|
+
|
||||||
|
.line 172
|
||||||
|
sget-object v0, Landroidx/compose/foundation/layout/ColumnScopeInstance;->INSTANCE:Landroidx/compose/foundation/layout/ColumnScopeInstance;
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user