Compare commits

..
18 Commits
Author SHA1 Message Date
meoware.exe 191dcca239 Merge pull request #64 from meowarex/dev
Waze Integration + Integrations UI <3
2026-07-03 20:20:14 +10:00
meoware.exe 08aee155c9 Integrations UI + Package Name Warnings/Gate <3 2026-07-03 20:19:16 +10:00
meoware.exe 47c9c37236 Merge pull request #63 from Se7en-dev/feat/waze-support-patch
[PATCH] Waze Integration
2026-07-03 15:46:39 +10:00
Se7en-dev 50b99b6193 feat(waze patch): customizable themeColor 2026-07-02 22:56:09 +02:00
Se7en-dev 03271bf26d feat(waze patch): Tidal cyan accent color for waze 2026-07-02 20:44:24 +02:00
Se7en-dev 75f8187526 feat(waze patch): waze media menu browse source options 2026-07-02 20:26:12 +02:00
Se7en-dev 523f0a342b feat(patch): waze integration patch 2026-07-02 15:49:18 +02:00
meoware.exe fb4c776c61 Merge pull request #60 from meowarex/dev
Fold Cover Everywhere -> Player Backdrop
2026-06-26 23:02:05 +10:00
meoware.exe cbf04d9fa7 Fold Cover Everywhere -> Player Backdrop 2026-06-26 23:01:26 +10:00
meoware.exe 266cd184dd Merge pull request #59 from meowarex/dev
Improve Swipe Gstures <3
2026-06-26 22:38:36 +10:00
meoware.exe 04ea66acdb Improve Swipe Gstures <3 2026-06-26 22:38:04 +10:00
meoware.exe 22526f4e0b Merge pull request #58 from Se7en-dev/fix/mini-player-gestures-swipe-up-android-gestures-nav-fix
Fix : Mini Player Gestures - Android gesture navigation swipe up gesture fix + enable drag option + visual swipe up feedback
2026-06-26 19:02:36 +10:00
Se7en-dev 5a3ad2df7a fix(MP gestures): axis locking + swipe up feedback reset fixes 2026-06-26 10:59:11 +02:00
Se7en-dev fa99d10134 lint(MP gestures enable drag option): remove now useless text 2026-06-26 00:00:12 +02:00
Se7en-dev 4e8af5960c fix(mini player gestures): scope swipe up gesture to track area 2026-06-25 23:57:46 +02:00
Se7en-dev 09f6e8d34f feat(mini player gestures): enable drag option 2026-06-25 21:58:27 +02:00
meoware.exe 160be11ac4 Merge pull request #55 from meowarex/dev
Improve Patch Option Style <3
2026-06-25 01:28:34 +10:00
meoware.exe 4486e52117 Improve Patch Option Style <3 2026-06-25 01:27:32 +10:00
29 changed files with 2312 additions and 198 deletions
@@ -6,6 +6,7 @@ import com.meowarex.rlmobile.patcher.steps.StepGroup
import com.meowarex.rlmobile.patcher.steps.base.Step
import com.meowarex.rlmobile.patcher.steps.download.CopyDependenciesStep
import com.meowarex.rlmobile.patcher.util.ManifestPatcher
import com.meowarex.rlmobile.ui.screens.patchopts.KnownPatch
import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptions
import com.github.diamondminer88.zip.ZipReader
import com.github.diamondminer88.zip.ZipWriter
@@ -26,11 +27,15 @@ class PatchManifestStep(private val options: PatchOptions) : Step() {
?: throw IllegalArgumentException("No manifest found in APK")
container.log("Patching manifest")
val enableWazeIntegration =
options.patchStates[KnownPatch.WazeIntegration.name] ?: KnownPatch.WazeIntegration.default.isEnabled
val patchedManifest = ManifestPatcher.patchManifest(
manifestBytes = manifest,
packageName = options.packageName,
appName = options.appName,
debuggable = options.debuggable,
enableWazeIntegration = enableWazeIntegration,
)
container.log("Repacking apk with patched manifest")
@@ -95,7 +95,19 @@ class SmaliPatchStep(
val entry = zip.openEntry(patchFile)
?: throw FileNotFoundException("Missing zip entry: $patchFile")
out.canonicalFile.parentFile?.mkdirs()
out.writeBytes(entry.read())
var smaliText = entry.read()
.decodeToString()
.replace("\r\n", "\n")
for ((token, value) in substitutions) {
if (smaliText.contains(token)) {
smaliText = smaliText.replace(token, value)
container.log("Applied substitution $token -> $value in $patchFile")
}
}
UNRESOLVED_TOKEN.find(smaliText)?.let { match ->
throw Error("Unresolved option placeholder ${match.value} in $patchFile")
}
out.writeText(smaliText)
container.log("Extracted extension smali: $relative")
continue
}
@@ -18,12 +18,16 @@ object ManifestPatcher {
private const val IS_SPLIT_REQUIRED = "isSplitRequired"
private const val REQUIRED_SPLIT_TYPES = "requiredSplitTypes"
private const val SPLIT_TYPES = "splitTypes"
private const val WAZE_PACKAGE = "com.waze"
private const val WAZE_INIT_RECEIVER = "radiant.WazeInitReceiver"
private const val WAZE_ACTION_INIT = "com.waze.sdk.audio.ACTION_INIT"
fun patchManifest(
manifestBytes: ByteArray,
packageName: String,
appName: String,
debuggable: Boolean,
enableWazeIntegration: Boolean,
): ByteArray {
// Extract original package name to rewrite every reference to it
// (permissions, provider authorities) to the new packageName.
@@ -84,6 +88,49 @@ object ManifestPatcher {
}
return when (name) {
"queries" -> object : NodeVisitor(nv) {
private var hasWazePackage = false
override fun child(ns: String?, name: String): NodeVisitor {
val visitor = super.child(ns, name)
return if (enableWazeIntegration && name == "package") {
object : NodeVisitor(visitor) {
override fun attr(
ns: String?,
name: String?,
resourceId: Int,
type: Int,
value: Any?,
) {
if (name == "name" && value == WAZE_PACKAGE) {
hasWazePackage = true
}
super.attr(ns, name, resourceId, type, value)
}
}
} else {
visitor
}
}
override fun end() {
if (enableWazeIntegration && !hasWazePackage) {
super.child(null, "package").apply {
attr(
ANDROID_NAMESPACE,
"name",
android.R.attr.name,
TYPE_STRING,
WAZE_PACKAGE,
)
end()
}
}
super.end()
}
}
"uses-permission" -> object : NodeVisitor(nv) {
override fun attr(ns: String?, name: String?, resourceId: Int, type: Int, value: Any?) {
if (name != "maxSdkVersion") {
@@ -132,6 +179,7 @@ object ManifestPatcher {
private var addUseEmbeddedDex = true
private var addExtractNativeLibs = true
private var addMetadata = true
private var hasWazeInitReceiver = false
override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) {
if (name == REQUEST_LEGACY_EXTERNAL_STORAGE) addLegacyStorage = false
@@ -161,6 +209,22 @@ object ManifestPatcher {
return when (name) {
"activity" -> ReplaceAttrsVisitor(visitor, mapOf("label" to appName))
"receiver" -> object : NodeVisitor(visitor) {
override fun attr(
ns: String?,
name: String,
resourceId: Int,
type: Int,
value: Any?
) {
if (name == "name" && value == WAZE_INIT_RECEIVER) {
hasWazeInitReceiver = true
}
super.attr(ns, name, resourceId, type, value)
}
}
"provider" -> object : NodeVisitor(visitor) {
override fun attr(
ns: String?,
@@ -219,6 +283,39 @@ object ManifestPatcher {
0
)
if (enableWazeIntegration && !hasWazeInitReceiver) {
super.child(null, "receiver").apply {
attr(
ANDROID_NAMESPACE,
"name",
android.R.attr.name,
TYPE_STRING,
WAZE_INIT_RECEIVER,
)
attr(
ANDROID_NAMESPACE,
"exported",
android.R.attr.exported,
TYPE_INT_BOOLEAN,
1,
)
child(null, "intent-filter").apply {
child(null, "action").apply {
attr(
ANDROID_NAMESPACE,
"name",
android.R.attr.name,
TYPE_STRING,
WAZE_ACTION_INIT,
)
end()
}
end()
}
end()
}
}
super.end()
}
}
@@ -35,12 +35,14 @@ private fun PatchOptionsScreenPreview(
packageName = parameters.packageName,
packageNameState = parameters.packageNameState,
setPackageName = {},
packageNameLocked = parameters.packageName == PatchOptions.Default.packageName,
onUnlockPackageName = {},
onLockPackageName = {},
customTidalApk = parameters.customTidalApk,
onSelectCustomTidalApk = {},
customPatches = parameters.customPatches,
onSelectCustomPatches = {},
specs = specs,
enabledPatchCount = specs.size,
isPatchEnabled = { true },
onTogglePatch = { _, _ -> },
patchLockState = { PatchLock.Free },
@@ -24,6 +24,8 @@ enum class KnownPatch(
val variants: List<PatchVariant> = emptyList(),
val defaultVariantIndex: Int = 0,
val advancedOptions: List<PatchOption> = emptyList(),
val category: String = PatchSpec.CATEGORY_PATCH,
val pathLocked: Boolean = false,
) {
LyricsDisableCover(
order = 41,
@@ -95,6 +97,18 @@ enum class KnownPatch(
token = "RL_SCRIM_ARGB",
encode = SmaliEncode(EncodeKind.ArgbAlpha),
),
PatchOption.Toggle(
key = "cover_everywhere",
titleRes = R.string.patch_cover_everywhere_title,
descRes = R.string.patch_cover_everywhere_desc,
default = false,
inline = true,
fileNames = listOf(
"home-backdrop.patch",
"collection-backdrop.patch",
"cover-capture.patch",
),
),
),
),
QualityBadgeColors(
@@ -111,17 +125,6 @@ enum class KnownPatch(
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(
order = 100,
fileNames = listOf("debug-menu-unlock.patch"),
@@ -129,6 +132,41 @@ enum class KnownPatch(
descRes = R.string.patch_debug_menu_unlock_desc,
default = Disabled,
),
WazeIntegration(
order = 90,
fileNames = listOf("waze-media-browser.patch"),
extensionFiles = listOf(
"radiant/WazeInitReceiver.smali",
"radiant/WazeServiceConnection.smali",
),
titleRes = R.string.patch_waze_integration_title,
descRes = R.string.patch_waze_integration_desc,
default = Disabled,
category = PatchSpec.CATEGORY_INTEGRATION,
pathLocked = true,
advancedOptions = listOf(
PatchOption.Choice(
key = "browse_root",
titleRes = R.string.patch_waze_browse_root_title,
entries = listOf(
ChoiceEntry(R.string.patch_waze_root_auto, value = "ROOT_AUTO"),
ChoiceEntry(R.string.patch_waze_root_home, value = "HOME_V2::home_page_v2_id"),
ChoiceEntry(
R.string.patch_waze_root_recents,
value = "RECENTLY_PLAYED::home/pages/CONTINUE_LISTEN_TO/view-all",
),
),
defaultIndex = 0,
token = "RL_WAZE_ROOT_ID",
),
PatchOption.Color(
key = "accent_color",
titleRes = R.string.patch_waze_accent_color_title,
default = -16747037, // Waze default #ff0075e3
token = "RL_WAZE_THEME_COLOR",
),
),
),
LyricsProgressPill(
order = 40,
fileNames = listOf(
@@ -148,15 +186,15 @@ enum class KnownPatch(
default = Disabled,
defaultVariantIndex = 2,
variants = listOf(
PatchVariant( // 0: Floating — stock rounded pill (no patch; the progress border is an option)
PatchVariant( // 0: Floating — stock rounded pill
titleRes = R.string.patch_mini_player_variant_floating_title,
fileNames = emptyList(),
),
PatchVariant( // 1: Grey — square, theme background (shown as "Legacy" when Dynamic BG is on)
PatchVariant( // 1: Grey — square
titleRes = R.string.patch_mini_player_variant_square_grey_title,
fileNames = listOf("mini-player-grey.patch"),
),
PatchVariant( // 2: Black — square, forced black background (hidden when Dynamic BG is on)
PatchVariant( // 2: Black — square black background
titleRes = R.string.patch_mini_player_variant_square_black_title,
fileNames = listOf("mini-player-black.patch"),
),
@@ -193,10 +231,24 @@ enum class KnownPatch(
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",
@@ -232,7 +284,6 @@ enum class KnownPatch(
PlayerBackdrop,
QualityBadgeColors,
LyricsProgressPill,
CoverEverywhere,
MiniPlayerRedesign,
),
);
@@ -32,6 +32,8 @@ sealed interface PatchOption {
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]. */
@@ -57,12 +59,23 @@ sealed interface PatchOption {
data class Choice(
override val key: String,
@StringRes override val titleRes: Int,
@StringRes override val descRes: Int,
@StringRes override val descRes: Int = 0,
val entries: List<ChoiceEntry>,
val defaultIndex: Int = 0,
val requiresOption: String? = null,
val token: String? = null,
) : PatchOption
data class Color(
override val key: String,
@StringRes override val titleRes: Int,
@StringRes override val descRes: Int = 0,
val default: Int,
val token: String? = null,
) : PatchOption
}
data class ChoiceEntry(
@StringRes val labelRes: Int,
val value: String? = null,
)
@@ -52,6 +52,13 @@ data class PatchOptions(
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 colorValue(spec: PatchSpec, option: OptionSpec.Color): Int =
optionInts["${spec.id}/${option.key}"] ?: option.default
fun isToggleOn(spec: PatchSpec, option: OptionSpec.Toggle): Boolean =
optionBools["${spec.id}/${option.key}"] ?: option.default
@@ -111,10 +118,31 @@ data class PatchOptions(
for (spec in specs) {
if (!isEnabled(spec)) continue
for (option in spec.advancedOptions) {
if (option !is OptionSpec.Slider) continue
val token = option.token ?: continue
val encode = option.encode ?: continue
put("__${token}__", encode.encode(sliderValue(spec, option)))
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)
}
is OptionSpec.Color -> {
val token = option.token ?: continue
put("__${token}__", colorValue(spec, option).toString())
}
}
}
}
}
@@ -43,6 +43,24 @@ class PatchOptionsModel(
fetchPkgNameStateDebounced()
}
/** Patches flagged [PatchSpec.pathLocked] only work under the stock package name. */
fun isBlockedByPackageName(spec: PatchSpec): Boolean =
spec.pathLocked && packageName != PatchOptions.Default.packageName
/** The package-name field starts locked & editing requires confirming the unlock dialog */
var packageNameLocked by mutableStateOf(prefilledOptions.packageName == PatchOptions.Default.packageName)
private set
fun unlockPackageName() {
packageNameLocked = false
}
/** Re-locking snaps the package name back to the stock default */
fun lockPackageName() {
packageNameLocked = true
changePackageName(PatchOptions.Default.packageName)
}
var appName by mutableStateOf(prefilledOptions.appName)
private set
@@ -83,7 +101,7 @@ class PatchOptionsModel(
.coerceIn(0, spec.variants.lastIndex.coerceAtLeast(0))
fun isPatchEnabled(spec: PatchSpec): Boolean =
patchStates[spec.id] ?: spec.defaultEnabled
!isBlockedByPackageName(spec) && (patchStates[spec.id] ?: spec.defaultEnabled)
fun setPatchEnabled(spec: PatchSpec, enabled: Boolean) {
val byId = specs.associateBy { it.id }
@@ -153,11 +171,19 @@ class PatchOptionsModel(
optionInts = optionInts + (keyOf(spec, option) to index)
}
fun colorValue(spec: PatchSpec, option: OptionSpec.Color): Int =
optionInts[keyOf(spec, option)] ?: option.default
fun setColorValue(spec: PatchSpec, option: OptionSpec.Color, value: Int) {
optionInts = optionInts + (keyOf(spec, option) to value)
}
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
is OptionSpec.Color -> colorValue(spec, option) != option.default
}
}
@@ -175,11 +201,14 @@ class PatchOptionsModel(
setSlider = ::setSliderValue,
choice = ::choiceValue,
setChoice = ::setChoiceValue,
color = ::colorValue,
setColor = ::setColorValue,
isModified = ::isAdvancedModified,
reset = ::resetAdvanced,
)
fun lockState(spec: PatchSpec): PatchLock {
if (isBlockedByPackageName(spec)) return PatchLock.RequiresDefaultPackage
if (spec.variants.isNotEmpty()) return PatchLock.Free
val byId = specs.associateBy { it.id }
@@ -206,9 +235,6 @@ class PatchOptionsModel(
return PatchLock.Free
}
val enabledPatchCount: Int
get() = specs.count { isPatchEnabled(it) }
var customTidalApk by mutableStateOf<PatchComponent?>(null)
private set
var customPatches by mutableStateOf<PatchComponent?>(null)
@@ -323,7 +349,10 @@ class PatchOptionsModel(
debuggable = debuggable,
customTidalApk = customTidalApk,
customPatches = customPatches,
patchStates = patchStates,
// Hard-disable package-name-bound patches so the patcher can't apply
patchStates = patchStates.toMutableMap().apply {
specs.filter { isBlockedByPackageName(it) }.forEach { this[it.id] = false }
},
selectedVariants = selectedVariants,
optionFloats = optionFloats,
optionBools = optionBools,
@@ -385,4 +414,7 @@ sealed class PatchLock {
data object Free : PatchLock()
data class LockedOn(val by: PatchSpec) : PatchLock()
data class LockedOff(val by: PatchSpec) : PatchLock()
/** Locked off because the install uses a non-default package name */
data object RequiresDefaultPackage : PatchLock()
}
@@ -19,7 +19,9 @@ import com.meowarex.rlmobile.R
import com.meowarex.rlmobile.ui.components.*
import com.meowarex.rlmobile.ui.screens.componentopts.PatchComponent
import com.meowarex.rlmobile.ui.screens.patching.PatchingScreen
import androidx.compose.runtime.saveable.rememberSaveable
import com.meowarex.rlmobile.ui.screens.patchopts.components.PackageNameStateLabel
import com.meowarex.rlmobile.ui.screens.patchopts.components.PackageNameUnlockDialog
import com.meowarex.rlmobile.ui.screens.patchopts.components.PatchOptionsAppBar
import com.meowarex.rlmobile.ui.screens.patchopts.components.PatchSelectionAccordion
import com.meowarex.rlmobile.ui.screens.patchopts.components.options.*
@@ -55,13 +57,16 @@ class PatchOptionsScreen(
packageNameState = model.packageNameState,
setPackageName = model::changePackageName,
packageNameLocked = model.packageNameLocked,
onUnlockPackageName = model::unlockPackageName,
onLockPackageName = model::lockPackageName,
customTidalApk = model.customTidalApk,
customPatches = model.customPatches,
onSelectCustomTidalApk = { model.selectCustomTidalApk(navigator) },
onSelectCustomPatches = { model.selectCustomPatches(navigator) },
specs = model.specs,
enabledPatchCount = model.enabledPatchCount,
isPatchEnabled = model::isPatchEnabled,
onTogglePatch = model::setPatchEnabled,
patchLockState = model::lockState,
@@ -93,13 +98,16 @@ fun PatchOptionsScreenContent(
packageNameState: PackageNameState,
setPackageName: (String) -> Unit,
packageNameLocked: Boolean,
onUnlockPackageName: () -> Unit,
onLockPackageName: () -> Unit,
customTidalApk: PatchComponent?,
onSelectCustomTidalApk: () -> Unit,
customPatches: PatchComponent?,
onSelectCustomPatches: () -> Unit,
specs: List<PatchSpec>,
enabledPatchCount: Int,
isPatchEnabled: (PatchSpec) -> Boolean,
onTogglePatch: (PatchSpec, Boolean) -> Unit,
patchLockState: (PatchSpec) -> PatchLock,
@@ -110,6 +118,18 @@ fun PatchOptionsScreenContent(
isConfigValid: Boolean,
onInstall: () -> Unit,
) {
var showPkgUnlockDialog by rememberSaveable { mutableStateOf(false) }
if (showPkgUnlockDialog) {
PackageNameUnlockDialog(
blockedTitles = specs.filter { it.pathLocked }.map { it.title },
onConfirm = {
showPkgUnlockDialog = false
onUnlockPackageName()
},
onDismiss = { showPkgUnlockDialog = false },
)
}
Scaffold(
topBar = { PatchOptionsAppBar(isUpdate = isUpdate) },
) { paddingValues ->
@@ -159,6 +179,10 @@ fun PatchOptionsScreenContent(
valueIsDefault = packageNameIsDefault,
onValueChange = setPackageName,
onValueReset = { setPackageName(PatchOptions.Default.packageName) },
locked = packageNameLocked,
onLockClick = {
if (packageNameLocked) showPkgUnlockDialog = true else onLockPackageName()
},
) {
PackageNameStateLabel(
state = packageNameState,
@@ -167,17 +191,40 @@ fun PatchOptionsScreenContent(
}
}
val (integrations, patches) = remember(specs) { specs.partition { it.isIntegration } }
if (integrations.isNotEmpty()) {
PatchSelectionAccordion(
iconRes = R.drawable.ic_extension,
titleRes = R.string.patchopts_integrations_title,
descriptionRes = R.string.patchopts_integrations_desc,
specs = integrations,
enabledCount = integrations.count(isPatchEnabled),
totalCount = integrations.size,
isEnabled = isPatchEnabled,
onToggle = onTogglePatch,
lockState = patchLockState,
variantIndex = variantIndex,
onSelectVariant = onSelectVariant,
optionState = optionState,
modifier = Modifier.padding(top = 4.dp),
)
}
PatchSelectionAccordion(
specs = specs,
enabledCount = enabledPatchCount,
totalCount = specs.size,
iconRes = R.drawable.ic_healing,
titleRes = R.string.patchopts_patches_title,
descriptionRes = R.string.patchopts_patches_desc,
specs = patches,
enabledCount = patches.count(isPatchEnabled),
totalCount = patches.size,
isEnabled = isPatchEnabled,
onToggle = onTogglePatch,
lockState = patchLockState,
variantIndex = variantIndex,
onSelectVariant = onSelectVariant,
optionState = optionState,
modifier = Modifier.padding(top = 4.dp),
modifier = if (integrations.isEmpty()) Modifier.padding(top = 4.dp) else Modifier,
)
if (isDevMode) {
@@ -29,7 +29,16 @@ data class PatchSpec(
val variants: List<VariantSpec> = emptyList(),
val defaultVariantIndex: Int = 0,
val advancedOptions: List<OptionSpec> = emptyList(),
)
val category: String = CATEGORY_PATCH,
val pathLocked: Boolean = false,
) {
val isIntegration: Boolean get() = category == CATEGORY_INTEGRATION
companion object {
const val CATEGORY_PATCH = "patch"
const val CATEGORY_INTEGRATION = "integration"
}
}
@Immutable
@Serializable
@@ -68,6 +77,8 @@ sealed interface OptionSpec {
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
@@ -96,10 +107,25 @@ sealed interface OptionSpec {
@SerialName("choice")
data class Choice(
override val key: String,
override val title: 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
@Immutable
@Serializable
@SerialName("color")
data class Color(
override val key: String,
override val title: String = "",
override val description: String = "",
val default: Int = 0,
/** Placeholder name (without the surrounding `__`) baked into the `.patch`/extension files. */
val token: String? = null,
) : OptionSpec
}
@@ -146,6 +172,8 @@ fun builtinPatchSpecs(resolve: (Int) -> String): List<PatchSpec> =
variants = patch.variants.map { VariantSpec(resolve(it.titleRes), it.fileNames, it.extensionFiles) },
defaultVariantIndex = patch.defaultVariantIndex,
advancedOptions = patch.advancedOptions.map { it.toSpec(resolve) },
category = patch.category,
pathLocked = patch.pathLocked,
)
}
@@ -162,6 +190,7 @@ private fun PatchOption.toSpec(resolve: (Int) -> String): OptionSpec = when (thi
requiresOption = requiresOption,
hidesVariants = hidesVariants,
relabelVariants = relabelVariants.mapValues { resolve(it.value) },
token = token,
)
is PatchOption.Slider -> OptionSpec.Slider(
@@ -181,9 +210,20 @@ private fun PatchOption.toSpec(resolve: (Int) -> String): OptionSpec = when (thi
is PatchOption.Choice -> OptionSpec.Choice(
key = key,
title = resolve(titleRes),
description = resolve(descRes),
description = if (descRes != 0) resolve(descRes) else "",
entries = entries.map { resolve(it.labelRes) },
defaultIndex = defaultIndex,
values = entries.map { it.value ?: "" },
requiresOption = requiresOption,
token = token,
)
is PatchOption.Color -> OptionSpec.Color(
key = key,
title = resolve(titleRes),
description = if (descRes != 0) resolve(descRes) else "",
default = default,
token = token,
)
}
@@ -249,6 +289,8 @@ class PatchOptionState(
val setSlider: (PatchSpec, OptionSpec.Slider, Float) -> Unit,
val choice: (PatchSpec, OptionSpec.Choice) -> Int,
val setChoice: (PatchSpec, OptionSpec.Choice, Int) -> Unit,
val color: (PatchSpec, OptionSpec.Color) -> Int,
val setColor: (PatchSpec, OptionSpec.Color, Int) -> Unit,
val isModified: (PatchSpec) -> Boolean,
val reset: (PatchSpec) -> Unit,
) {
@@ -261,6 +303,8 @@ class PatchOptionState(
setSlider = { _, _, _ -> },
choice = { _, option -> option.defaultIndex },
setChoice = { _, _, _ -> },
color = { _, option -> option.default },
setColor = { _, _, _ -> },
isModified = { false },
reset = {},
)
@@ -0,0 +1,88 @@
package com.meowarex.rlmobile.ui.screens.patchopts.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.fromHtml
import androidx.compose.ui.unit.dp
import com.meowarex.rlmobile.R
import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptions
/**
* Confirms unlocking the package-name field, warning that a custom name
* blocks the listed patches/integrations from working.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PackageNameUnlockDialog(
blockedTitles: List<String>,
onConfirm: () -> Unit,
onDismiss: () -> Unit,
) {
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.patchopts_pkgname_warning_title),
style = MaterialTheme.typography.titleLarge,
)
Text(
text = AnnotatedString.fromHtml(
stringResource(R.string.patchopts_pkgname_warning_msg, PatchOptions.Default.packageName),
),
style = MaterialTheme.typography.bodyLarge,
)
if (blockedTitles.isNotEmpty()) {
Column(
verticalArrangement = Arrangement.spacedBy(2.dp),
modifier = Modifier.padding(start = 4.dp),
) {
for (title in blockedTitles) {
Text(
text = "$title",
style = MaterialTheme.typography.bodyLarge,
fontWeight = FontWeight.Bold,
)
}
}
}
Text(
text = stringResource(R.string.patchopts_pkgname_warning_permanent),
style = MaterialTheme.typography.bodyLarge,
)
Box(modifier = Modifier.fillMaxWidth()) {
Row(modifier = Modifier.align(Alignment.CenterEnd)) {
TextButton(onClick = onDismiss) {
Text(stringResource(R.string.action_cancel))
}
TextButton(onClick = onConfirm) {
Text(stringResource(R.string.action_continue))
}
}
}
}
}
}
}
@@ -1,9 +1,14 @@
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.shape.CircleShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import androidx.compose.runtime.*
@@ -11,11 +16,16 @@ 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.graphics.Color
import androidx.compose.ui.graphics.toArgb
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.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
@@ -87,18 +97,91 @@ fun PatchAdvancedOptionsSheet(
HorizontalDivider()
val sheetOptions = patch.advancedOptions.filter { it !is OptionSpec.Toggle || !it.inline }
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 -> ToggleOptionRow(
title = option.title,
description = option.description,
checked = state.toggle(patch, option),
onCheckedChange = { state.setToggle(patch, option, it) },
locked = lock != OptionLock.Free,
onLockedTap = { lockDialog = lock },
)
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,
@@ -110,12 +193,20 @@ fun PatchAdvancedOptionsSheet(
onValueChange = { state.setSlider(patch, option, it) },
)
is OptionSpec.Choice -> ChoiceOptionRow(
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) },
)
}
is OptionSpec.Color -> ColorOptionRow(
title = option.title,
description = option.description,
entries = option.entries,
selectedIndex = state.choice(patch, option),
onSelect = { state.setChoice(patch, option, it) },
color = state.color(patch, option),
onColorChange = { state.setColor(patch, option, it) },
)
}
}
@@ -147,13 +238,14 @@ private fun ToggleOptionRow(
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
modifier = modifier
.fillMaxWidth()
.clickable(
interactionSource = interactionSource,
@@ -279,18 +371,58 @@ private fun snapToNearestStep(
@Composable
private fun ChoiceOptionRow(
title: String,
description: String,
entries: List<String>,
selectedIndex: Int,
onSelect: (Int) -> Unit,
title: String = "",
modifier: Modifier = Modifier,
) {
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(6.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
OptionText(title = title, description = description)
if (title.isNotEmpty()) {
Text(
text = title,
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center,
)
}
if (entries.size > 3) {
var expanded by rememberSaveable { mutableStateOf(false) }
val selectedLabel = entries.getOrNull(selectedIndex).orEmpty()
Box(modifier = Modifier.fillMaxWidth()) {
OutlinedButton(
onClick = { expanded = true },
modifier = Modifier.fillMaxWidth(),
) {
Text(
text = selectedLabel,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier.fillMaxWidth(),
) {
entries.forEachIndexed { index, entry ->
DropdownMenuItem(
text = { Text(entry) },
onClick = {
onSelect(index)
expanded = false
},
)
}
}
}
return@Column
}
SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) {
entries.forEachIndexed { index, entry ->
SegmentedButton(
@@ -313,6 +445,150 @@ private fun ChoiceOptionRow(
}
}
@Composable
private fun ColorOptionRow(
title: String,
color: Int,
onColorChange: (Int) -> Unit,
) {
var showPicker by rememberSaveable { mutableStateOf(false) }
val selectedColor = color.withOpaqueAlpha()
val materialYouColor = MaterialTheme.colorScheme.primary.toArgb().withOpaqueAlpha()
val presets = listOf(
stringResource(R.string.patch_waze_color_waze_default),
stringResource(R.string.patch_waze_color_tidal_cyan),
stringResource(R.string.patch_waze_color_material_you),
)
val presetColors = listOf(WAZE_DEFAULT, TIDAL_CYAN, materialYouColor)
val selectedPreset = presetColors.indexOf(selectedColor)
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Text(
text = title,
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.align(Alignment.CenterHorizontally),
)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(10.dp),
modifier = Modifier
.fillMaxWidth()
.clip(MaterialTheme.shapes.medium)
.clickable { showPicker = true }
.padding(vertical = 4.dp),
) {
ColorSwatch(color = selectedColor)
Text(
text = selectedColor.toRgbHex(),
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.primary,
)
}
SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) {
presets.forEachIndexed { index, label ->
SegmentedButton(
selected = index == selectedPreset,
onClick = { onColorChange(presetColors[index]) },
shape = SegmentedButtonDefaults.itemShape(index = index, count = presets.size),
icon = {},
label = {
Text(
text = label,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.labelMedium,
)
},
)
}
}
}
if (showPicker) {
ColorPickerDialog(
initialColor = selectedColor,
onDismiss = { showPicker = false },
onColorSelected = {
onColorChange(it)
showPicker = false
},
)
}
}
@Composable
private fun ColorSwatch(color: Int, modifier: Modifier = Modifier) {
Box(
modifier = modifier
.size(32.dp)
.clip(CircleShape)
.background(Color(color))
.border(1.dp, MaterialTheme.colorScheme.outlineVariant, CircleShape),
)
}
@Composable
private fun ColorPickerDialog(
initialColor: Int,
onDismiss: () -> Unit,
onColorSelected: (Int) -> Unit,
) {
var color by rememberSaveable(initialColor) { mutableIntStateOf(initialColor.withOpaqueAlpha()) }
var hex by rememberSaveable(initialColor) { mutableStateOf(color.toRgbHex()) }
val parsedHex = parseRgbHex(hex)
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(stringResource(R.string.patch_color_picker_title)) },
text = {
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
ColorSwatch(color = parsedHex ?: color, modifier = Modifier.size(40.dp))
OutlinedTextField(
value = hex,
onValueChange = { value ->
hex = value
parseRgbHex(value)?.let { color = it }
},
label = { Text(stringResource(R.string.patch_color_picker_hex)) },
singleLine = true,
isError = parsedHex == null,
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.Characters,
keyboardType = KeyboardType.Ascii,
),
supportingText = if (parsedHex == null) {
{ Text(stringResource(R.string.patch_color_picker_hex_error)) }
} else null,
modifier = Modifier.weight(1f),
)
}
}
},
confirmButton = {
TextButton(
enabled = parsedHex != null,
onClick = { parsedHex?.let(onColorSelected) },
) {
Text(stringResource(R.string.action_done))
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text(stringResource(R.string.action_cancel))
}
},
)
}
@Composable
private fun OptionText(
title: String,
@@ -345,6 +621,26 @@ private fun formatSliderValue(option: OptionSpec.Slider, value: Float): String {
}
}
private const val OPAQUE_ALPHA = -0x1000000
private const val WAZE_DEFAULT = -16747037 // #ff0075e3
private const val TIDAL_CYAN = -14549268 // #ff21feec
private fun Int.withOpaqueAlpha(): Int = (this and 0x00FFFFFF) or OPAQUE_ALPHA
private fun Int.toRgbHex(): String = "#" +
(this and 0x00FFFFFF).toString(16).uppercase().padStart(6, '0')
private fun parseRgbHex(value: String): Int? {
val clean = value.trim()
.removePrefix("#")
.removePrefix("0x")
.removePrefix("0X")
if (clean.length != 6 && clean.length != 8) return null
val parsed = clean.toLongOrNull(16) ?: return null
val rgb = if (clean.length == 8) parsed and 0x00FFFFFFL else parsed
return (0xFF000000L or rgb).toInt()
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun OptionLockDialog(lock: OptionLock, onDismiss: () -> Unit) {
@@ -1,9 +1,12 @@
package com.meowarex.rlmobile.ui.screens.patchopts.components
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
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.*
@@ -26,6 +29,7 @@ import com.meowarex.rlmobile.R
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.PatchOptions
import com.meowarex.rlmobile.ui.screens.patchopts.PatchSpec
import com.meowarex.rlmobile.ui.screens.patchopts.effectiveVariants
import com.meowarex.rlmobile.ui.screens.patchopts.resolveVariantIndex
@@ -34,6 +38,9 @@ private data class LockInfo(val patch: PatchSpec, val lock: PatchLock)
@Composable
fun PatchSelectionAccordion(
@DrawableRes iconRes: Int,
@StringRes titleRes: Int,
@StringRes descriptionRes: Int,
specs: List<PatchSpec>,
enabledCount: Int,
totalCount: Int,
@@ -68,12 +75,17 @@ fun PatchSelectionAccordion(
.clickable(role = Role.Button) { expanded = !expanded }
.padding(horizontal = 16.dp, vertical = 14.dp),
) {
Icon(
painter = painterResource(iconRes),
contentDescription = null,
)
Column(
verticalArrangement = Arrangement.spacedBy(2.dp),
modifier = Modifier.weight(1f),
) {
Text(
text = stringResource(R.string.patchopts_patches_title),
text = stringResource(titleRes),
style = MaterialTheme.typography.titleMedium,
)
Text(
@@ -105,7 +117,7 @@ fun PatchSelectionAccordion(
.padding(horizontal = 16.dp, vertical = 12.dp),
) {
Text(
text = stringResource(R.string.patchopts_patches_desc),
text = stringResource(descriptionRes),
style = MaterialTheme.typography.bodySmall,
modifier = Modifier
.alpha(.7f)
@@ -115,75 +127,85 @@ fun PatchSelectionAccordion(
for (patch in specs) key(patch.id) {
val checked = isEnabled(patch)
val lock = lockState(patch)
PatchSwitchRow(
title = patch.title,
description = patch.description,
checked = checked,
lock = lock,
onCheckedChange = { onToggle(patch, it) },
onLockedTap = { lockInfo = LockInfo(patch, lock) },
)
if (patch.variants.isNotEmpty()) {
AnimatedVisibility(visible = checked) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(start = 4.dp, end = 4.dp, top = 4.dp, bottom = 4.dp),
) {
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) }
},
)
}
}
}
val inlineToggles = patch.advancedOptions
.filterIsInstance<OptionSpec.Toggle>()
.filter { it.inline }
if (inlineToggles.isNotEmpty()) {
AnimatedVisibility(visible = checked) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 4.dp),
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
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) },
)
}
}
}
}
val hasSheetOptions = patch.advancedOptions.any { it !is OptionSpec.Toggle || !it.inline }
if (hasSheetOptions) {
AnimatedVisibility(visible = checked) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
contentAlignment = Alignment.Center,
) {
AdvancedOptionsButton(
modified = optionState.isModified(patch),
onClick = { advancedFor = patch },
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 },
)
}
}
}
}
}
}
@@ -361,6 +383,11 @@ private fun PatchLockDialog(
R.string.patch_lock_blocked_msg,
lock.by.title,
)
PatchLock.RequiresDefaultPackage -> Triple(
R.string.patch_lock_pkgname_title,
R.string.patch_lock_pkgname_msg,
PatchOptions.Default.packageName,
)
PatchLock.Free -> return
}
@@ -380,7 +407,7 @@ private fun PatchLockDialog(
)
Text(
text = AnnotatedString.fromHtml(stringResource(msgRes, blockerTitle)),
style = MaterialTheme.typography.titleMedium,
style = MaterialTheme.typography.bodyLarge,
)
Box(modifier = Modifier.fillMaxWidth()) {
TextButton(
@@ -3,8 +3,12 @@ package com.meowarex.rlmobile.ui.screens.patchopts.components.options
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.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.meowarex.rlmobile.R
import com.meowarex.rlmobile.ui.components.Label
import com.meowarex.rlmobile.ui.components.ResetToDefaultButton
@@ -18,6 +22,8 @@ fun TextPatchOption(
onValueChange: (String) -> Unit,
onValueReset: () -> Unit,
modifier: Modifier = Modifier,
locked: Boolean? = null,
onLockClick: () -> Unit = {},
extra: (@Composable ColumnScope.() -> Unit)? = null,
) {
Label(
@@ -30,16 +36,41 @@ fun TextPatchOption(
onValueChange = onValueChange,
isError = valueIsError,
singleLine = true,
enabled = locked != true,
colors = OutlinedTextFieldDefaults.colors(
unfocusedContainerColor = MaterialTheme.colorScheme.surfaceContainerLow,
focusedContainerColor = MaterialTheme.colorScheme.surfaceContainerLow,
errorContainerColor = MaterialTheme.colorScheme.surfaceContainerLow,
disabledContainerColor = MaterialTheme.colorScheme.surfaceContainerLow,
),
trailingIcon = {
ResetToDefaultButton(
enabled = !valueIsDefault,
onClick = onValueReset,
)
Row(verticalAlignment = Alignment.CenterVertically) {
if (locked != true) {
ResetToDefaultButton(
enabled = !valueIsDefault,
onClick = onValueReset,
)
}
if (locked != null) {
IconButton(
onClick = onLockClick,
modifier = Modifier.size(32.dp),
) {
Icon(
painter = painterResource(
if (locked) R.drawable.ic_lock else R.drawable.ic_lock_open,
),
contentDescription = stringResource(
if (locked) R.string.patchopts_field_unlock else R.string.patchopts_field_lock,
),
tint = if (locked) MaterialTheme.colorScheme.primary
else MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.size(18.dp),
)
}
Spacer(Modifier.width(8.dp))
}
}
},
modifier = Modifier
.fillMaxWidth()
@@ -0,0 +1,9 @@
<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="M218,856L104,742Q81,719 81,686Q81,653 104,630L630,104Q653,81 686,81Q719,81 742,104L856,218Q879,241 879,274Q879,307 856,330L330,856Q307,879 274,879Q241,879 218,856ZM508,734L734,508L856,630Q879,653 879,686Q879,719 856,742L742,856Q719,879 686,879Q653,879 630,856L508,734ZM480,600Q497,600 508.5,588.5Q520,577 520,560Q520,543 508.5,531.5Q497,520 480,520Q463,520 451.5,531.5Q440,543 440,560Q440,577 451.5,588.5Q463,600 480,600ZM400,520Q417,520 428.5,508.5Q440,497 440,480Q440,463 428.5,451.5Q417,440 400,440Q383,440 371.5,451.5Q360,463 360,480Q360,497 371.5,508.5Q383,520 400,520ZM560,520Q577,520 588.5,508.5Q600,497 600,480Q600,463 588.5,451.5Q577,440 560,440Q543,440 531.5,451.5Q520,463 520,480Q520,497 531.5,508.5Q543,520 560,520ZM225,451L104,330Q81,307 81,274Q81,241 104,218L218,104Q241,81 274,81Q307,81 330,104L452,226L225,451ZM480,440Q497,440 508.5,428.5Q520,417 520,400Q520,383 508.5,371.5Q497,360 480,360Q463,360 451.5,371.5Q440,383 440,400Q440,417 451.5,428.5Q463,440 480,440Z" />
</vector>
@@ -252,9 +252,16 @@
<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_summary">%1$d of %2$d enabled</string>
<string name="patchopts_integrations_title">Integrations</string>
<string name="patchopts_integrations_desc">Connect TIDAL to other systems/apps</string>
<string name="patchopts_pkgname_warning_title">Custom Package Name</string>
<string name="patchopts_pkgname_warning_msg">Installing under a package name other than &lt;b>%s&lt;/b> will block the following from working:</string>
<string name="patchopts_pkgname_warning_permanent">The package name is permanent once installed (requires a fresh install)</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="patchopts_field_unlock">Unlock editing</string>
<string name="patchopts_field_lock">Lock and reset to default</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>
@@ -286,6 +293,19 @@
<string
name="patch_debug_menu_unlock_desc"
>Reveals TIDAL\'s hidden Debug Menu in Settings. (Unlocks Feature Flags &amp; the legacy Debug Options)</string>
<string name="patch_waze_integration_title">Waze Integration</string>
<string name="patch_waze_integration_desc">Control TIDAL playback from the Waze app</string>
<string name="patch_waze_browse_root_title">Media Menu</string>
<string name="patch_waze_accent_color_title">Accent Color</string>
<string name="patch_waze_color_waze_default">Waze Default</string>
<string name="patch_waze_color_tidal_cyan">TIDAL Cyan</string>
<string name="patch_waze_color_material_you">Material You</string>
<string name="patch_waze_root_auto">Auto Menu</string>
<string name="patch_waze_root_home">Home</string>
<string name="patch_waze_root_recents">Recents</string>
<string name="patch_color_picker_title">Custom color</string>
<string name="patch_color_picker_hex">Hex color</string>
<string name="patch_color_picker_hex_error">Use #RRGGBB or #AARRGGBB.</string>
<string name="patch_enable_legacy_ui_title">Enable Legacy UI</string>
<string
name="patch_enable_legacy_ui_desc"
@@ -295,6 +315,10 @@
<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>
@@ -316,6 +340,8 @@
<string name="patch_lock_blocked_title">Patch Blocked!</string>
<string name="patch_lock_required_msg">Patch Required by &lt;b>%s&lt;/b>.</string>
<string name="patch_lock_blocked_msg">Patch Blocked by &lt;b>%s&lt;/b>.</string>
<string name="patch_lock_pkgname_title">Original Package Name Invalid!</string>
<string name="patch_lock_pkgname_msg">This only works while the app keeps its original &lt;b>%s&lt;/b> package name. Reset the Package Name option to enable it.&lt;br/>&lt;br/>An existing install can\'t be renamed (requires a fresh install)</string>
<string name="patch_opt_lock_title">Option Locked</string>
<string name="patch_opt_lock_needs_variant">Requires the &lt;b>%s&lt;/b> style.</string>
<string name="patch_opt_lock_needs_option">Requires the &lt;b>%s&lt;/b> option.</string>
+1 -1
View File
@@ -1,5 +1,5 @@
{
"tidalVersionCode": 9090,
"tidalApkUrl": "https://github.com/meowarex/rl-mobile/releases/download/latest/tidal-stock.apk",
"patchesVersion": "0.9.12"
"patchesVersion": "0.9.15"
}
@@ -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
@@ -38,9 +38,12 @@
# 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;)V
.method public constructor <init>(Lyl0/l;Landroidx/compose/material3/SheetState;Z)V
.locals 0
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
@@ -49,6 +52,8 @@
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
@@ -150,6 +155,10 @@
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
@@ -158,6 +167,7 @@
invoke-direct {p0}, Lradiant/MiniPlayerGestures$Gesture;->openPlayer()V
:done
return-void
.end method
@@ -189,6 +199,8 @@
# 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
@@ -221,6 +233,12 @@
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
@@ -253,8 +271,17 @@
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
@@ -281,28 +308,60 @@
move-result v8
const/high16 v9, 0x41800000 # 16.0f
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, :vertical_check
if-gez v10, :horizontal_dominance
const/high16 v10, 0x3fc00000 # 1.5f
goto :vertical_check
:horizontal_dominance
const v10, 0x3f8ccccd # 1.1f
mul-float/2addr v10, v8
cmpl-float v10, v7, v10
cmpg-float v10, v7, v10
if-lez v10, :vertical_check
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
@@ -313,10 +372,12 @@
if-lez v10, :vertical_dominance
invoke-static {v2}, Lradiant/MiniPlayerGestures;->setSwipeFeedbackFromDrag(F)V
goto :store_last
:vertical_dominance
const/high16 v10, 0x3fc00000 # 1.5f -> has to dominate horizontal mov. by 1.5x
const v10, 0x3f8ccccd # 1.1f -> early vertical axis lock
mul-float/2addr v7, v10
@@ -327,10 +388,29 @@
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
@@ -345,7 +425,11 @@
iget-boolean v0, p0, Lradiant/MiniPlayerGestures$Gesture;->e:Z
if-eqz v0, :finish
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
@@ -367,6 +451,28 @@
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
@@ -377,7 +483,9 @@
:cancel
iget-boolean v0, p0, Lradiant/MiniPlayerGestures$Gesture;->e:Z
if-eqz v0, :cancel_reset
if-eqz v0, :cancel_no_active
invoke-static {}, Lradiant/MiniPlayerGestures;->animateSwipeFeedbackReset()V
iget-object v0, p0, Lradiant/MiniPlayerGestures$Gesture;->f:Landroidx/compose/material3/SheetState;
@@ -385,6 +493,11 @@
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
@@ -40,16 +40,16 @@
iget-object v0, p0, Lradiant/MiniPlayerGestures$RootGesture;->b:Landroidx/compose/material3/SheetState;
invoke-static {v0}, Lradiant/MiniPlayerGestures;->isDragging(Landroidx/compose/material3/SheetState;)Z
move-result v1
if-eqz v1, :done
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
@@ -64,6 +64,51 @@
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
@@ -102,6 +147,8 @@
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
@@ -110,6 +157,8 @@
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;
@@ -50,8 +50,43 @@
# Mini player media state
.field private static r:Z
# AppScaffold context
.field private static s:Lyl0/l;
.field private static t:Landroidx/compose/material3/SheetState;
.field private static u:Z
# Feedback offset
.field public static v:Landroidx/compose/runtime/MutableFloatState;
.field private static w:Landroid/animation/ValueAnimator;
# Gesture axis lock: 0 = none, 1 = vertical, 2 = horizontal
.field private static x:I
# Swipe gesture start Y
.field private static y:F
# Relative to Y drag offset
.field private static z:F
# 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/MiniPlayerGestures;->v:Landroidx/compose/runtime/MutableFloatState;
return-void
.end method
.method private constructor <init>()V
.locals 0
@@ -60,6 +95,119 @@
return-void
.end method
.method public static animateSwipeFeedbackReset()V
.locals 7
sget-object v0, Lradiant/MiniPlayerGestures;->w:Landroid/animation/ValueAnimator;
if-eqz v0, :read_offset
invoke-virtual {v0}, Landroid/animation/ValueAnimator;->cancel()V
const/4 v0, 0x0
sput-object v0, Lradiant/MiniPlayerGestures;->w:Landroid/animation/ValueAnimator;
:read_offset
sget-object v0, Lradiant/MiniPlayerGestures;->v: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/MiniPlayerGestures$FeedbackResetAnimator;
invoke-direct {v0}, Lradiant/MiniPlayerGestures$FeedbackResetAnimator;-><init>()V
invoke-virtual {v1, v0}, Landroid/animation/ValueAnimator;->addUpdateListener(Landroid/animation/ValueAnimator$AnimatorUpdateListener;)V
sput-object v1, Lradiant/MiniPlayerGestures;->w:Landroid/animation/ValueAnimator;
invoke-virtual {v1}, Landroid/animation/ValueAnimator;->start()V
return-void
:snap_zero
invoke-static {v3}, Lradiant/MiniPlayerGestures;->setSwipeFeedbackDirect(F)V
return-void
.end method
.method public static beginSwipeFeedback()V
.locals 1
sget-object v0, Lradiant/MiniPlayerGestures;->w:Landroid/animation/ValueAnimator;
if-eqz v0, :zero
invoke-virtual {v0}, Landroid/animation/ValueAnimator;->cancel()V
const/4 v0, 0x0
sput-object v0, Lradiant/MiniPlayerGestures;->w:Landroid/animation/ValueAnimator;
:zero
const/4 v0, 0x0
invoke-static {v0}, Lradiant/MiniPlayerGestures;->setSwipeFeedbackDirect(F)V
return-void
.end method
.method public static beginSwipeGesture(F)V
.locals 1
sput p0, Lradiant/MiniPlayerGestures;->y:F
const/4 v0, 0x0
sput v0, Lradiant/MiniPlayerGestures;->z:F
invoke-static {}, Lradiant/MiniPlayerGestures;->beginSwipeFeedback()V
return-void
.end method
.method public static applyPendingDrag(Landroidx/compose/material3/SheetState;)V
.locals 5
@@ -473,6 +621,114 @@
sget-boolean v0, Lradiant/MiniPlayerGestures;->b:Z
if-nez v0, :yes
invoke-static {}, Lradiant/MiniPlayerGestures;->isHorizontalGestureSuppressed()Z
move-result v0
return v0
:yes
const/4 v0, 0x1
return v0
.end method
.method public static isHorizontalGestureSuppressed()Z
.locals 2
sget v0, Lradiant/MiniPlayerGestures;->x:I
const/4 v1, 0x1
if-ne v0, v1, :no
const/4 v0, 0x1
return v0
:no
const/4 v0, 0x0
return v0
.end method
.method public static isHorizontalGestureLocked()Z
.locals 2
sget v0, Lradiant/MiniPlayerGestures;->x:I
const/4 v1, 0x2
if-ne v0, v1, :no
const/4 v0, 0x1
return v0
:no
const/4 v0, 0x0
return v0
.end method
.method public static lockHorizontalGesture()Z
.locals 2
sget v0, Lradiant/MiniPlayerGestures;->x:I
const/4 v1, 0x1
if-ne v0, v1, :lock
const/4 v0, 0x0
return v0
:lock
const/4 v0, 0x2
sput v0, Lradiant/MiniPlayerGestures;->x:I
const/4 v0, 0x1
return v0
.end method
.method public static resetGestureLock()V
.locals 1
const/4 v0, 0x0
sput v0, Lradiant/MiniPlayerGestures;->x:I
sput v0, Lradiant/MiniPlayerGestures;->y:F
sput v0, Lradiant/MiniPlayerGestures;->z:F
return-void
.end method
.method public static lockVerticalGesture()Z
.locals 2
sget v0, Lradiant/MiniPlayerGestures;->x:I
const/4 v1, 0x2
if-ne v0, v1, :lock
const/4 v0, 0x0
return v0
:lock
const/4 v0, 0x1
sput v0, Lradiant/MiniPlayerGestures;->x:I
return v0
.end method
@@ -492,6 +748,63 @@
return-void
.end method
.method public static configure(Lyl0/l;Landroidx/compose/material3/SheetState;Z)V
.locals 0
sput-object p0, Lradiant/MiniPlayerGestures;->s:Lyl0/l;
sput-object p1, Lradiant/MiniPlayerGestures;->t:Landroidx/compose/material3/SheetState;
sput-boolean p2, Lradiant/MiniPlayerGestures;->u:Z
return-void
.end method
.method public static clearSwipeFeedbackAnimator(Landroid/animation/ValueAnimator;)V
.locals 1
sget-object v0, Lradiant/MiniPlayerGestures;->w:Landroid/animation/ValueAnimator;
if-ne v0, p0, :done
const/4 v0, 0x0
sput-object v0, Lradiant/MiniPlayerGestures;->w:Landroid/animation/ValueAnimator;
:done
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 public static feedbackModifier(Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier;
.locals 1
sget-object v0, Lradiant/MiniPlayerGestures$FeedbackLayer;->INSTANCE:Lradiant/MiniPlayerGestures$FeedbackLayer;
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 private static clampReleaseVelocity(F)F
.locals 4
@@ -535,12 +848,12 @@
return p0
.end method
.method public static modifier(Landroidx/compose/ui/Modifier;Lyl0/l;Landroidx/compose/material3/SheetState;)Landroidx/compose/ui/Modifier;
.method public static modifier(Landroidx/compose/ui/Modifier;Lyl0/l;Landroidx/compose/material3/SheetState;Z)Landroidx/compose/ui/Modifier;
.locals 1
new-instance v0, Lradiant/MiniPlayerGestures$Gesture;
invoke-direct {v0, p1, p2}, Lradiant/MiniPlayerGestures$Gesture;-><init>(Lyl0/l;Landroidx/compose/material3/SheetState;)V
invoke-direct {v0, p1, p2, p3}, Lradiant/MiniPlayerGestures$Gesture;-><init>(Lyl0/l;Landroidx/compose/material3/SheetState;Z)V
invoke-static {p0, v0}, Landroidx/compose/ui/input/pointer/PointerInteropFilter_androidKt;->motionEventSpy(Landroidx/compose/ui/Modifier;Lyl0/l;)Landroidx/compose/ui/Modifier;
@@ -549,6 +862,249 @@
return-object p0
.end method
.method public static trackAreaModifier(Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier;
.locals 3
sget-object v0, Lradiant/MiniPlayerGestures;->s:Lyl0/l;
if-eqz v0, :done
sget-object v1, Lradiant/MiniPlayerGestures;->t:Landroidx/compose/material3/SheetState;
if-eqz v1, :done
sget-boolean v2, Lradiant/MiniPlayerGestures;->u:Z
invoke-static {p0, v0, v1, v2}, Lradiant/MiniPlayerGestures;->modifier(Landroidx/compose/ui/Modifier;Lyl0/l;Landroidx/compose/material3/SheetState;Z)Landroidx/compose/ui/Modifier;
move-result-object p0
:done
return-object p0
.end method
.method public static setSwipeFeedback(F)V
.locals 5
const/4 v4, 0x0
cmpl-float v0, p0, v4
if-gtz v0, :snap_zero
sget-object v0, Lradiant/MiniPlayerGestures;->w:Landroid/animation/ValueAnimator;
if-eqz v0, :rubber_band
invoke-virtual {v0}, Landroid/animation/ValueAnimator;->cancel()V
const/4 v0, 0x0
sput-object v0, Lradiant/MiniPlayerGestures;->w:Landroid/animation/ValueAnimator;
:rubber_band
invoke-static {p0}, Ljava/lang/Math;->abs(F)F
move-result v0
const/high16 v1, 0x41800000 # 16.0f dp travel
invoke-static {v1}, Lradiant/MiniPlayerGestures;->dp(F)F
move-result v1
cmpg-float v2, v0, v1
if-lez v2, :publish
sub-float/2addr v0, v1
const v2, 0x3e19999a # 0.15f resistance after 16dp
mul-float/2addr v0, v2
const/high16 v2, 0x3d800000 # 0.0625f max up travel
mul-float/2addr v2, v1
invoke-static {v0, v2}, Ljava/lang/Math;->min(FF)F
move-result v0
add-float/2addr v0, v1
neg-float p0, v0
:publish
invoke-static {p0}, Lradiant/MiniPlayerGestures;->setSwipeFeedbackDirect(F)V
return-void
:snap_zero
invoke-static {v4}, Lradiant/MiniPlayerGestures;->setSwipeFeedbackDirect(F)V
return-void
.end method
.method public static setSwipeFeedbackFromDrag(F)V
.locals 3
sput p0, Lradiant/MiniPlayerGestures;->z:F
const/4 v0, 0x0
cmpl-float v1, p0, v0
if-ltz v1, :maybe_up
invoke-static {v0}, Lradiant/MiniPlayerGestures;->setSwipeFeedbackDirect(F)V
return-void
:maybe_up
const/high16 v0, 0x40c00000 # 6.0f feedback deadzone
invoke-static {v0}, Lradiant/MiniPlayerGestures;->dp(F)F
move-result v0
neg-float v1, v0
cmpg-float v2, p0, v1
if-gtz v2, :snap_zero
add-float/2addr p0, v0
invoke-static {p0}, Lradiant/MiniPlayerGestures;->setSwipeFeedback(F)V
return-void
:snap_zero
const/4 v0, 0x0
invoke-static {v0}, Lradiant/MiniPlayerGestures;->setSwipeFeedbackDirect(F)V
return-void
.end method
.method public static completeSimpleSwipe(Lyl0/l;F)Z
.locals 4
sget-boolean v0, Lradiant/MiniPlayerGestures;->u:Z
if-nez v0, :no
sget v0, Lradiant/MiniPlayerGestures;->x:I
const/4 v1, 0x1
if-ne v0, v1, :no
sget-boolean v0, Lradiant/MiniPlayerGestures;->r:Z
if-eqz v0, :consume_no
sget v0, Lradiant/MiniPlayerGestures;->y:F
sub-float/2addr p1, v0
sput p1, Lradiant/MiniPlayerGestures;->z:F
const/high16 v0, 0x42400000 # 48.0f release threshold
invoke-static {v0}, Lradiant/MiniPlayerGestures;->dp(F)F
move-result v0
neg-float v0, v0
cmpg-float v2, p1, v0
if-lez v2, :open
:consume_no
invoke-static {}, Lradiant/MiniPlayerGestures;->resetGestureLock()V
const/4 v0, 0x0
return v0
:open
invoke-static {}, Lradiant/MiniPlayerGestures;->resetGestureLock()V
sget-object v0, Lcom/tidal/android/feature/appscaffold/ui/b$b;->a:Lcom/tidal/android/feature/appscaffold/ui/b$b;
invoke-interface {p0, v0}, Lyl0/l;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
const/4 v0, 0x1
return v0
:no
const/4 v0, 0x0
return v0
.end method
.method public static updateSimpleSwipe(F)Z
.locals 3
sget-boolean v0, Lradiant/MiniPlayerGestures;->u:Z
if-nez v0, :no
sget v0, Lradiant/MiniPlayerGestures;->x:I
const/4 v1, 0x1
if-ne v0, v1, :no
sget v0, Lradiant/MiniPlayerGestures;->y:F
sub-float/2addr p0, v0
invoke-static {p0}, Lradiant/MiniPlayerGestures;->setSwipeFeedbackFromDrag(F)V
const/4 v0, 0x1
return v0
:no
const/4 v0, 0x0
return v0
.end method
.method public static setSwipeFeedbackDirect(F)V
.locals 1
sget-object v0, Lradiant/MiniPlayerGestures;->v:Landroidx/compose/runtime/MutableFloatState;
invoke-interface {v0, p0}, Landroidx/compose/runtime/MutableFloatState;->setFloatValue(F)V
return-void
.end method
.method public static suppressHorizontalGestures()V
.locals 2
sget v0, Lradiant/MiniPlayerGestures;->x:I
const/4 v1, 0x2
if-ne v0, v1, :lock
return-void
:lock
const/4 v0, 0x1
sput v0, Lradiant/MiniPlayerGestures;->x:I
return-void
.end method
.method private static minFlingVelocity()F
.locals 2
@@ -1007,6 +1563,8 @@
:apply
invoke-static {p1}, Lradiant/MiniPlayerGestures;->setSwipeFeedbackFromDrag(F)V
invoke-static {p0}, Lradiant/MiniPlayerGestures;->applyPendingDrag(Landroidx/compose/material3/SheetState;)V
const/4 v0, 0x3
@@ -126,6 +126,8 @@
.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
@@ -188,6 +190,24 @@
.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
@@ -336,17 +356,38 @@
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
@@ -0,0 +1,87 @@
.class public final Lradiant/WazeInitReceiver;
.super Landroid/content/BroadcastReceiver;
.source "WazeInitReceiver.smali"
# direct methods
.method public constructor <init>()V
.locals 0
invoke-direct {p0}, Landroid/content/BroadcastReceiver;-><init>()V
return-void
.end method
# virtual methods
.method public onReceive(Landroid/content/Context;Landroid/content/Intent;)V
.locals 5
if-eqz p1, :return
if-eqz p2, :return
const-string v0, "com.waze.sdk.audio.ACTION_INIT"
invoke-virtual {p2}, Landroid/content/Intent;->getAction()Ljava/lang/String;
move-result-object v1
invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
move-result v0
if-eqz v0, :return
const-string v0, "token"
invoke-virtual {p2, v0}, Landroid/content/Intent;->getStringExtra(Ljava/lang/String;)Ljava/lang/String;
move-result-object v0
if-eqz v0, :return
invoke-virtual {v0}, Ljava/lang/String;->length()I
move-result v1
if-lez v1, :return
invoke-virtual {p1}, Landroid/content/Context;->getApplicationContext()Landroid/content/Context;
move-result-object v1
if-eqz v1, :context_ready
move-object p1, v1
:context_ready
new-instance v1, Landroid/content/Intent;
invoke-direct {v1}, Landroid/content/Intent;-><init>()V
const-string v2, "com.waze"
const-string v3, "com.waze.sdk.SdkService"
invoke-virtual {v1, v2, v3}, Landroid/content/Intent;->setClassName(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;
new-instance v2, Lradiant/WazeServiceConnection;
invoke-direct {v2, p1, v0}, Lradiant/WazeServiceConnection;-><init>(Landroid/content/Context;Ljava/lang/String;)V
:try_start_0
const/4 v3, 0x1
invoke-virtual {p1, v1, v2, v3}, Landroid/content/Context;->bindService(Landroid/content/Intent;Landroid/content/ServiceConnection;I)Z
:try_end_0
.catch Ljava/lang/Throwable; {:try_start_0 .. :try_end_0} :catch_0
goto :return
:catch_0
move-exception v4
:return
return-void
.end method
@@ -0,0 +1,228 @@
.class public final Lradiant/WazeServiceConnection;
.super Ljava/lang/Object;
.source "WazeServiceConnection.smali"
# interfaces
.implements Landroid/content/ServiceConnection;
# instance fields
.field private final context:Landroid/content/Context;
.field private final token:Ljava/lang/String;
# direct methods
.method public constructor <init>(Landroid/content/Context;Ljava/lang/String;)V
.locals 0
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
iput-object p1, p0, Lradiant/WazeServiceConnection;->context:Landroid/content/Context;
iput-object p2, p0, Lradiant/WazeServiceConnection;->token:Ljava/lang/String;
return-void
.end method
.method private addOpenMeIntent(Landroid/os/Bundle;)V
.locals 4
iget-object v0, p0, Lradiant/WazeServiceConnection;->context:Landroid/content/Context;
invoke-virtual {v0}, Landroid/content/Context;->getPackageManager()Landroid/content/pm/PackageManager;
move-result-object v1
invoke-virtual {v0}, Landroid/content/Context;->getPackageName()Ljava/lang/String;
move-result-object v2
invoke-virtual {v1, v2}, Landroid/content/pm/PackageManager;->getLaunchIntentForPackage(Ljava/lang/String;)Landroid/content/Intent;
move-result-object v1
if-eqz v1, :return
const/high16 v2, 0x10000000
invoke-virtual {v1, v2}, Landroid/content/Intent;->addFlags(I)Landroid/content/Intent;
const v2, 0xc000000
const/4 v3, 0x0
invoke-static {v0, v3, v1, v2}, Landroid/app/PendingIntent;->getActivity(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;
move-result-object v1
const-string v2, "openMeIntent"
invoke-virtual {p1, v2, v1}, Landroid/os/Bundle;->putParcelable(Ljava/lang/String;Landroid/os/Parcelable;)V
:return
return-void
.end method
.method private createClientMessenger()Landroid/os/Messenger;
.locals 3
new-instance v0, Landroid/os/Handler;
invoke-static {}, Landroid/os/Looper;->getMainLooper()Landroid/os/Looper;
move-result-object v1
invoke-direct {v0, v1}, Landroid/os/Handler;-><init>(Landroid/os/Looper;)V
new-instance v2, Landroid/os/Messenger;
invoke-direct {v2, v0}, Landroid/os/Messenger;-><init>(Landroid/os/Handler;)V
return-object v2
.end method
.method private createConfigBundle()Landroid/os/Bundle;
.locals 3
new-instance v0, Landroid/os/Bundle;
invoke-direct {v0}, Landroid/os/Bundle;-><init>()V
const-string v1, "versionCode"
const/16 v2, 0xa
invoke-virtual {v0, v1, v2}, Landroid/os/Bundle;->putInt(Ljava/lang/String;I)V
const-string v1, "themeColor"
const v2, __RL_WAZE_THEME_COLOR__ # Waze SDK themeColor
invoke-virtual {v0, v1, v2}, Landroid/os/Bundle;->putInt(Ljava/lang/String;I)V
invoke-direct {p0, v0}, Lradiant/WazeServiceConnection;->addOpenMeIntent(Landroid/os/Bundle;)V
return-object v0
.end method
.method private register(Landroid/os/IBinder;)V
.locals 5
if-eqz p1, :return
invoke-static {}, Landroid/os/Parcel;->obtain()Landroid/os/Parcel;
move-result-object v0
invoke-static {}, Landroid/os/Parcel;->obtain()Landroid/os/Parcel;
move-result-object v1
const-string v2, "com.waze.sdk.ISdkService"
invoke-virtual {v0, v2}, Landroid/os/Parcel;->writeInterfaceToken(Ljava/lang/String;)V
iget-object v2, p0, Lradiant/WazeServiceConnection;->token:Ljava/lang/String;
invoke-virtual {v0, v2}, Landroid/os/Parcel;->writeString(Ljava/lang/String;)V
invoke-direct {p0}, Lradiant/WazeServiceConnection;->createConfigBundle()Landroid/os/Bundle;
move-result-object v2
invoke-static {v0, v2}, Lradiant/WazeServiceConnection;->writeParcelable(Landroid/os/Parcel;Landroid/os/Parcelable;)V
invoke-direct {p0}, Lradiant/WazeServiceConnection;->createClientMessenger()Landroid/os/Messenger;
move-result-object v2
invoke-static {v0, v2}, Lradiant/WazeServiceConnection;->writeParcelable(Landroid/os/Parcel;Landroid/os/Parcelable;)V
const/4 v2, 0x1
const/4 v3, 0x0
invoke-interface {p1, v2, v0, v1, v3}, Landroid/os/IBinder;->transact(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z
move-result v4
if-eqz v4, :cleanup
invoke-virtual {v1}, Landroid/os/Parcel;->readException()V
:cleanup
invoke-virtual {v1}, Landroid/os/Parcel;->recycle()V
invoke-virtual {v0}, Landroid/os/Parcel;->recycle()V
:return
return-void
.end method
.method private safeUnbind()V
.locals 1
:try_start_0
iget-object v0, p0, Lradiant/WazeServiceConnection;->context:Landroid/content/Context;
invoke-virtual {v0, p0}, Landroid/content/Context;->unbindService(Landroid/content/ServiceConnection;)V
:try_end_0
.catch Ljava/lang/Throwable; {:try_start_0 .. :try_end_0} :catch_0
goto :return
:catch_0
move-exception v0
:return
return-void
.end method
.method private static writeParcelable(Landroid/os/Parcel;Landroid/os/Parcelable;)V
.locals 2
const/4 v0, 0x0
if-eqz p1, :null_value
const/4 v1, 0x1
invoke-virtual {p0, v1}, Landroid/os/Parcel;->writeInt(I)V
invoke-interface {p1, p0, v0}, Landroid/os/Parcelable;->writeToParcel(Landroid/os/Parcel;I)V
return-void
:null_value
invoke-virtual {p0, v0}, Landroid/os/Parcel;->writeInt(I)V
return-void
.end method
# virtual methods
.method public onServiceConnected(Landroid/content/ComponentName;Landroid/os/IBinder;)V
.locals 1
:try_start_0
invoke-direct {p0, p2}, Lradiant/WazeServiceConnection;->register(Landroid/os/IBinder;)V
:try_end_0
.catch Ljava/lang/Throwable; {:try_start_0 .. :try_end_0} :catch_0
goto :unbind
:catch_0
move-exception v0
:unbind
invoke-direct {p0}, Lradiant/WazeServiceConnection;->safeUnbind()V
return-void
.end method
.method public onServiceDisconnected(Landroid/content/ComponentName;)V
.locals 0
return-void
.end method
+58 -13
View File
@@ -18,7 +18,6 @@
"PlayerBackdrop",
"QualityBadgeColors",
"LyricsProgressPill",
"CoverEverywhere",
"MiniPlayerRedesign"
]
},
@@ -35,14 +34,14 @@
"order": 30,
"fileNames": ["player-backdrop.patch"],
"title": "Player Backdrop",
"description": "Restores the legacy translucent backdrop blur behind the player.",
"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. 0% is sharp, 50% matches the original look.",
"description": "How strongly the album art behind the player is blurred",
"default": 50,
"min": 0,
"max": 100,
@@ -55,7 +54,7 @@
"type": "slider",
"key": "dimming",
"title": "Backdrop Dimming",
"description": "Darkens the backdrop so the foreground controls stay legible.",
"description": "Darkens the backdrop so the foreground controls stay legible",
"default": 50,
"min": 0,
"max": 100,
@@ -63,17 +62,18 @@
"displayAsPercent": true,
"token": "RL_SCRIM_ARGB",
"encode": { "kind": "argbAlpha" }
},
{
"type": "toggle",
"key": "cover_everywhere",
"title": "Cover Everywhere - WIP",
"description": "Applies the blurred backdrop to every Compose based page (Home & Collection)",
"default": false,
"inline": true,
"fileNames": ["home-backdrop.patch", "collection-backdrop.patch", "cover-capture.patch"]
}
]
},
{
"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,
@@ -163,16 +163,28 @@
"type": "toggle",
"key": "gestures",
"title": "Mini-Player Gestures",
"description": "Swipe up on the mini-player to open the full player.",
"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",
@@ -200,6 +212,39 @@
"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": "WazeIntegration",
"order": 90,
"category": "integration",
"pathLocked": true,
"fileNames": ["waze-media-browser.patch"],
"extensionFiles": ["radiant/WazeInitReceiver.smali", "radiant/WazeServiceConnection.smali"],
"title": "Waze Integration",
"description": "Control TIDAL playback from the Waze app",
"default": true,
"advancedOptions": [
{
"type": "choice",
"key": "browse_root",
"title": "Media Menu",
"entries": ["Auto Menu", "Home", "Recents"],
"values": [
"ROOT_AUTO",
"HOME_V2::home_page_v2_id",
"RECENTLY_PLAYED::home/pages/CONTINUE_LISTEN_TO/view-all"
],
"defaultIndex": 0,
"token": "RL_WAZE_ROOT_ID"
},
{
"type": "color",
"key": "accent_color",
"title": "Accent Color",
"default": -16747037,
"token": "RL_WAZE_THEME_COLOR"
}
]
},
{
"id": "DebugMenuUnlock",
"order": 100,
@@ -1,16 +1,14 @@
--- a/com/tidal/android/feature/appscaffold/ui/composable/MiniPlayerKt.smali
+++ b/com/tidal/android/feature/appscaffold/ui/composable/MiniPlayerKt.smali
@@ -1746,6 +1746,10 @@
invoke-static {v9, v10, v15, v6, v8}, Landroidx/compose/foundation/layout/PaddingKt;->padding-qDBjuR0(Landroidx/compose/ui/Modifier;FFFF)Landroidx/compose/ui/Modifier;
@@ -1768,3 +1768,7 @@
move-result-object v6
-
+
+ invoke-static {v6, v1}, Lradiant/MiniPlayerTrackGestures;->trackModifier(Landroidx/compose/ui/Modifier;Lyl0/l;)Landroidx/compose/ui/Modifier; # attach horizontal swipe listener
+
+ move-result-object v6
+
.line 38
sget-object v21, Landroidx/compose/ui/Alignment;->Companion:Landroidx/compose/ui/Alignment$Companion;
@@ -1884,5 +1888,5 @@
.line 56
iget-object v6, v0, Lcom/tidal/android/feature/appscaffold/ui/s;->b:Lcom/tidal/android/feature/appscaffold/ui/p;
+34 -36
View File
@@ -1,41 +1,10 @@
--- a/com/tidal/android/feature/appscaffold/ui/composable/i.smali
+++ b/com/tidal/android/feature/appscaffold/ui/composable/i.smali
@@ -2832,27 +2832,5 @@
- .line 150
- sget-object v8, Lkotlin/u;->a:Lkotlin/u;
-
- .line 151
- invoke-interface {v15}, Landroidx/compose/runtime/Composer;->rememberedValue()Ljava/lang/Object;
-
- move-result-object v10
-
- .line 152
- invoke-virtual/range {v22 .. v22}, Landroidx/compose/runtime/Composer$Companion;->getEmpty()Ljava/lang/Object;
-
- move-result-object v11
-
- if-ne v10, v11, :cond_48
-
- .line 153
- sget-object v10, Lcom/tidal/android/feature/appscaffold/ui/composable/AppScaffoldKt$AppScaffold$3$2$1$1;->a:Lcom/tidal/android/feature/appscaffold/ui/composable/AppScaffoldKt$AppScaffold$3$2$1$1;
-
- invoke-interface {v15, v10}, Landroidx/compose/runtime/Composer;->updateRememberedValue(Ljava/lang/Object;)V
-
- .line 154
- :cond_48
- check-cast v10, Landroidx/compose/ui/input/pointer/PointerInputEventHandler;
-
- invoke-static {v0, v8, v10}, Landroidx/compose/ui/input/pointer/SuspendingPointerInputFilterKt;->pointerInput(Landroidx/compose/ui/Modifier;Ljava/lang/Object;Landroidx/compose/ui/input/pointer/PointerInputEventHandler;)Landroidx/compose/ui/Modifier;
+ move-object/from16 v8, v25 # SheetState from AppScaffold
+
+ invoke-static {v0, v6, v8}, Lradiant/MiniPlayerGestures;->modifier(Landroidx/compose/ui/Modifier;Lyl0/l;Landroidx/compose/material3/SheetState;)Landroidx/compose/ui/Modifier; # attach swipe up listener
move-result-object v0
@@ -2427,6 +2406,12 @@
@@ -2427,6 +2427,12 @@
move-object/from16 v16, v9
.line 99
+ move-object/from16 v9, v25 # same SheetState used by the modal sheet
+ move-object/from16 v9, v25 # SheetState
+
+ invoke-static {v5, v6, v9}, Lradiant/MiniPlayerGestures;->rootModifier(Landroidx/compose/ui/Modifier;Lyl0/l;Landroidx/compose/material3/SheetState;)Landroidx/compose/ui/Modifier; # continue drag after player recomposition
+
@@ -44,7 +13,15 @@
invoke-static {v15, v5}, Landroidx/compose/ui/ComposedModifierKt;->materializeModifier(Landroidx/compose/runtime/Composer;Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier;
move-result-object v9
@@ -2832,2 +2838,8 @@
+ move-object/from16 v8, v25 # SheetState from AppScaffold
+
+ const/4 v10, __RL_MINI_PLAYER_SWIPE_UP_DRAG__ # Enable Drag option
+
+ invoke-static {v6, v8, v10}, Lradiant/MiniPlayerGestures;->configure(Lyl0/l;Landroidx/compose/material3/SheetState;Z)V
+
.line 150
sget-object v8, Lkotlin/u;->a:Lkotlin/u;
--- a/com/tidal/android/feature/appscaffold/ui/composable/MiniPlayerKt.smali
+++ b/com/tidal/android/feature/appscaffold/ui/composable/MiniPlayerKt.smali
@@ -1243,5 +1243,21 @@
@@ -67,9 +44,30 @@
+
+ :rl_set_media
+ invoke-static {v5}, Lradiant/MiniPlayerGestures;->setHasMiniPlayerMedia(Z)V
-
+
invoke-virtual {v1}, Ljava/lang/Object;->getClass()Ljava/lang/Class;
@@ -1750,2 +1766,6 @@
+ invoke-static {v6}, Lradiant/MiniPlayerGestures;->trackAreaModifier(Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier; # attach swipe up listener
+
+ move-result-object v6
+
.line 38
sget-object v21, Landroidx/compose/ui/Alignment;->Companion:Landroidx/compose/ui/Alignment$Companion;
@@ -1923,2 +1943,6 @@
+ invoke-static {v4}, Lradiant/MiniPlayerGestures;->feedbackModifier(Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier; # move cover (swipe up feedback)
+
+ move-result-object v4
+
.line 63
invoke-virtual/range {v22 .. v22}, Lcom/squareup/ui/market/core/theme/MarketStylesheet$c;->p0()Lc20/a;
@@ -2034,2 +2058,6 @@
+ invoke-static {v4}, Lradiant/MiniPlayerGestures;->feedbackModifier(Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier; # move title/artist (swipe up feedback)
+
+ move-result-object v4
+
.line 72
invoke-virtual/range {p2 .. p2}, Landroidx/compose/foundation/layout/Arrangement;->getTop()Landroidx/compose/foundation/layout/Arrangement$Vertical;
--- a/androidx/compose/material3/ModalBottomSheetKt$ModalBottomSheet$4$1.smali
+++ b/androidx/compose/material3/ModalBottomSheetKt$ModalBottomSheet$4$1.smali
@@ -210,8 +210,10 @@
+59
View File
@@ -0,0 +1,59 @@
--- a/pb/c.smali
+++ b/pb/c.smali
@@ -801,6 +801,26 @@
-
+
.line 236
:goto_8
+ const-string/jumbo v5, "com.waze"
+
+ invoke-static {v3, v5}, Lkotlin/jvm/internal/n;->b(Ljava/lang/Object;Ljava/lang/Object;)Z # package match
+
+ move-result v5
+
+ if-eqz v5, :rl_waze_done
+
+ const-string/jumbo v5, "03:63:7f:6c:5d:8f:60:4e:6f:db:79:a6:ff:bf:a5:78:de:4e:31:8f:8d:a2:2f:c6:10:66:65:24:7f:88:07:d7" # Waze cert
+
+ invoke-static {v2, v5}, Lkotlin/jvm/internal/n;->b(Ljava/lang/Object;Ljava/lang/Object;)Z # cert match
+
+ move-result v5
+
+ if-eqz v5, :rl_waze_done
+
+ goto :goto_9 # trust Waze
+
+ :rl_waze_done
+
invoke-static {}, Landroid/os/Process;->myUid()I
-
+
.line 237
@@ -957,6 +977,26 @@
:goto_b
if-eqz v2, :cond_14
-
+
+ const-string/jumbo v1, "com.waze"
+
+ invoke-static {v3, v1}, Lkotlin/jvm/internal/n;->b(Ljava/lang/Object;Ljava/lang/Object;)Z # package match
+
+ move-result v1
+
+ if-eqz v1, :rl_waze_recent_root_done_2
+
+ new-instance v1, Landroidx/media/MediaBrowserServiceCompat$BrowserRoot;
+
+ const-string/jumbo v4, "__RL_WAZE_ROOT_ID__"
+
+ const/4 v5, 0x0
+
+ invoke-direct {v1, v4, v5}, Landroidx/media/MediaBrowserServiceCompat$BrowserRoot;-><init>(Ljava/lang/String;Landroid/os/Bundle;)V
+
+ return-object v1
+
+ :rl_waze_recent_root_done_2
+
.line 306
.line 307
iget-object v1, v0, Lpb/c;->c:Ljava/util/Map;