fix: Flutter migration (#2946)

This commit is contained in:
Jānis
2026-02-19 23:57:23 +02:00
committed by GitHub
parent 4f65555b56
commit 31a0f751aa
3 changed files with 71 additions and 50 deletions

View File

@@ -135,15 +135,15 @@ android {
buildToolsVersion = "35.0.1"
defaultConfig {
applicationId = "app.revanced.manager"
applicationId = "app.revanced.manager.flutter"
minSdk = 26
targetSdk = 35
val versionStr = if (version == "unspecified") "1.0.0" else version.toString()
versionName = versionStr
versionCode = with(versionStr.toVersion()) {
major * 10_000_000 +
minor * 10_000 +
major * 100_000_000 +
minor * 100_000 +
patch * 100 +
(preRelease?.substringAfterLast('.')?.toInt() ?: 99)
}

View File

@@ -1,12 +1,9 @@
package app.revanced.manager
import android.content.ActivityNotFoundException
import android.os.Bundle
import android.os.Parcelable
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.slideInHorizontally
@@ -62,6 +59,7 @@ import org.koin.androidx.compose.koinViewModel
import org.koin.core.parameter.parametersOf
import org.koin.androidx.viewmodel.ext.android.getViewModel as getActivityViewModel
class MainActivity : AppCompatActivity() {
@ExperimentalAnimationApi
override fun onCreate(savedInstanceState: Bundle?) {
@@ -74,21 +72,10 @@ class MainActivity : AppCompatActivity() {
val vm: MainViewModel = getActivityViewModel()
setContent {
val launcher = rememberLauncherForActivityResult(
ActivityResultContracts.StartActivityForResult(),
onResult = vm::applyLegacySettings
)
val theme by vm.prefs.theme.getAsState()
val dynamicColor by vm.prefs.dynamicColor.getAsState()
val pureBlackTheme by vm.prefs.pureBlackTheme.getAsState()
EventEffect(vm.legacyImportActivityFlow) {
try {
launcher.launch(it)
} catch (_: ActivityNotFoundException) {
}
}
ReVancedManagerTheme(
darkTheme = theme == Theme.SYSTEM && isSystemInDarkTheme() || theme == Theme.DARK,
dynamicColor = dynamicColor,

View File

@@ -1,18 +1,19 @@
package app.revanced.manager.ui.viewmodel
import android.app.Activity
import android.app.Application
import android.content.Intent
import android.util.Base64
import android.content.Context.MODE_PRIVATE
import android.content.SharedPreferences
import android.util.Log
import androidx.activity.result.ActivityResult
import androidx.core.content.edit
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.revanced.manager.R
import app.revanced.manager.data.room.apps.installed.InstallType
import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.asRemoteOrNull
import app.revanced.manager.domain.manager.KeystoreManager
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.domain.repository.DownloadedAppRepository
import app.revanced.manager.domain.repository.InstalledAppRepository
import app.revanced.manager.domain.repository.PatchBundleRepository
import app.revanced.manager.domain.repository.PatchSelectionRepository
import app.revanced.manager.domain.repository.SerializedSelection
@@ -27,11 +28,16 @@ import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json
import org.json.JSONObject
import java.io.File
private const val LEGACY_LIST_PREFIX = "VGhpcyBpcyB0aGUgcHJlZml4IGZvciBhIGxpc3Qu!"
class MainViewModel(
private val patchBundleRepository: PatchBundleRepository,
private val patchSelectionRepository: PatchSelectionRepository,
private val downloadedAppRepository: DownloadedAppRepository,
private val installedAppRepository: InstalledAppRepository,
private val keystoreManager: KeystoreManager,
private val app: Application,
val prefs: PreferencesManager,
@@ -39,8 +45,6 @@ class MainViewModel(
) : ViewModel() {
private val appSelectChannel = Channel<SelectedApp>()
val appSelectFlow = appSelectChannel.receiveAsFlow()
private val legacyImportActivityChannel = Channel<Intent>()
val legacyImportActivityFlow = legacyImportActivityChannel.receiveAsFlow()
private suspend fun suggestedVersion(packageName: String) =
patchBundleRepository.suggestedVersions.first()[packageName]
@@ -72,43 +76,55 @@ class MainViewModel(
init {
viewModelScope.launch {
if (!prefs.firstLaunch.get()) return@launch
legacyImportActivityChannel.send(Intent().apply {
setClassName(
"app.revanced.manager.flutter",
"app.revanced.manager.flutter.ExportSettingsActivity"
)
})
val flutterPrefs = app.getSharedPreferences("FlutterSharedPreferences", MODE_PRIVATE)
if (flutterPrefs.all.isNotEmpty()) applyLegacySettings(flutterPrefs)
}
}
fun applyLegacySettings(result: ActivityResult) {
if (result.resultCode != Activity.RESULT_OK) {
app.toast(app.getString(R.string.legacy_import_failed))
Log.e(
tag,
"Got unknown result code while importing legacy settings: ${result.resultCode}"
)
return
fun applyLegacySettings(flutterPrefs: SharedPreferences) {
Log.d(tag, "Migrating flutter preferences")
val data = JSONObject().apply {
put("keystorePassword", "s3cur3p@ssw0rd")
val allEntries: Map<String, *> = flutterPrefs.all
for ((key, value) in allEntries) {
put(key.replace("flutter.", ""), value)
}
}
val jsonStr = result.data?.getStringExtra("data")
if (jsonStr == null) {
app.toast(app.getString(R.string.legacy_import_failed))
Log.e(tag, "Legacy settings data is null")
return
val storedPatchesFile = File(app.filesDir.parentFile.absolutePath, "/app_flutter/selected-patches.json")
val patches: SerializedSelection? =
if (storedPatchesFile.exists()) {
json.decodeFromString<SerializedSelection>(storedPatchesFile.readText())
} else {
null
}
val keystoreFile = File(app.getExternalFilesDir(null), "/revanced-manager.keystore")
val keystore: ByteArray? = if (keystoreFile.exists()) {
val bytes = keystoreFile.readBytes()
keystoreFile.delete()
bytes
} else {
null
}
flutterPrefs.edit(commit = true) { clear() }
val settings = try {
json.decodeFromString<LegacySettings>(jsonStr)
json.decodeFromString<LegacySettings>(data.toString())
} catch (e: SerializationException) {
app.toast(app.getString(R.string.legacy_import_failed))
Log.e(tag, "Legacy settings data could not be deserialized", e)
return
}
applyLegacySettings(settings)
applyLegacySettings(settings, patches, keystore)
}
private fun applyLegacySettings(settings: LegacySettings) = viewModelScope.launch {
private fun applyLegacySettings(settings: LegacySettings, patches: SerializedSelection?, keystore: ByteArray?) = viewModelScope.launch {
settings.themeMode?.let { theme ->
val themeMap = mapOf(
0 to Theme.SYSTEM,
@@ -144,20 +160,39 @@ class MainViewModel(
settings.patchesChangeEnabled?.let { disableSelectionWarning ->
prefs.disableSelectionWarning.update(disableSelectionWarning)
}
settings.keystore?.let { keystore ->
val keystoreBytes = Base64.decode(keystore, Base64.DEFAULT)
keystore?.let { keystoreBytes ->
keystoreManager.import(
"alias",
settings.keystorePassword,
keystoreBytes.inputStream()
)
}
settings.patches?.let { selection ->
patches?.let { selection ->
patchSelectionRepository.import(0, selection)
}
settings.patchedApps?.let { apps ->
json.decodeFromString<List<String>>(apps.removePrefix(LEGACY_LIST_PREFIX)).forEach { appJson ->
val patchedApp = json.decodeFromString<LegacyPatchedApp>(appJson)
installedAppRepository.addOrUpdate(
patchedApp.packageName,
patchedApp.packageName,
patchedApp.version,
if (patchedApp.isRooted) InstallType.MOUNT else InstallType.DEFAULT,
mapOf(0 to patchedApp.appliedPatches.toSet())
)
}
}
Log.d(tag, "Imported legacy settings")
}
@Serializable
private data class LegacyPatchedApp(
val packageName: String,
val version: String,
val isRooted: Boolean,
val appliedPatches: List<String>,
)
@Serializable
private data class LegacySettings(
val keystorePassword: String,
@@ -168,7 +203,6 @@ class MainViewModel(
val experimentalPatchesEnabled: Boolean? = null,
val patchesAutoUpdate: Boolean? = null,
val patchesChangeEnabled: Boolean? = null,
val keystore: String? = null,
val patches: SerializedSelection? = null,
val patchedApps: String? = null,
)
}