add string resources and fix webview bugs

This commit is contained in:
Ax333l 2024-12-19 20:17:56 +01:00
parent d0cd29d625
commit 829f093afe
No known key found for this signature in database
GPG Key ID: D2B4D85271127D23
9 changed files with 73 additions and 48 deletions

View File

@ -76,7 +76,7 @@ class PatcherWorker(
val logger: Logger, val logger: Logger,
val downloadProgress: MutableStateFlow<Pair<Double, Double?>?>, val downloadProgress: MutableStateFlow<Pair<Double, Double?>?>,
val patchesProgress: MutableStateFlow<Pair<Int, Int>>, val patchesProgress: MutableStateFlow<Pair<Int, Int>>,
val handleStartActivityRequest: suspend (Intent) -> ActivityResult, val handleStartActivityRequest: suspend (LoadedDownloaderPlugin, Intent) -> ActivityResult,
val setInputFile: (File) -> Unit, val setInputFile: (File) -> Unit,
val onProgress: ProgressEventHandler val onProgress: ProgressEventHandler
) { ) {
@ -182,7 +182,7 @@ class PatcherWorker(
override val pluginPackageName = plugin.packageName override val pluginPackageName = plugin.packageName
override val hostPackageName = applicationContext.packageName override val hostPackageName = applicationContext.packageName
override suspend fun requestStartActivity(intent: Intent): Intent? { override suspend fun requestStartActivity(intent: Intent): Intent? {
val result = args.handleStartActivityRequest(intent) val result = args.handleStartActivityRequest(plugin, intent)
return when (result.resultCode) { return when (result.resultCode) {
Activity.RESULT_OK -> result.data Activity.RESULT_OK -> result.data
Activity.RESULT_CANCELED -> throw UserInteractionException.Activity.Cancelled() Activity.RESULT_CANCELED -> throw UserInteractionException.Activity.Cancelled()

View File

@ -96,7 +96,7 @@ fun PatcherScreen(
activityLauncher.launch(intent) activityLauncher.launch(intent)
} }
if (vm.showActivityPromptDialog) vm.activityPromptDialog?.let { title ->
AlertDialog( AlertDialog(
onDismissRequest = vm::rejectInteraction, onDismissRequest = vm::rejectInteraction,
confirmButton = { confirmButton = {
@ -113,11 +113,12 @@ fun PatcherScreen(
Text(stringResource(R.string.cancel)) Text(stringResource(R.string.cancel))
} }
}, },
title = { Text("User interaction required.") }, title = { Text(title) },
text = { text = {
Text("User interaction is required to proceed.") Text(stringResource(R.string.plugin_activity_dialog_body))
} }
) )
}
AppScaffold( AppScaffold(
topBar = { topBar = {

View File

@ -273,7 +273,7 @@ private fun AppSourceSelectorDialog(
Text(stringResource(R.string.cancel)) Text(stringResource(R.string.cancel))
} }
}, },
title = { Text("Select source") }, title = { Text(stringResource(R.string.app_source_dialog_title)) },
textHorizontalPadding = PaddingValues(horizontal = 0.dp), textHorizontalPadding = PaddingValues(horizontal = 0.dp),
text = { text = {
LazyColumn { LazyColumn {
@ -283,8 +283,15 @@ private fun AppSourceSelectorDialog(
modifier = Modifier modifier = Modifier
.clickable(enabled = canSelect && hasPlugins) { onSelect(searchApp) } .clickable(enabled = canSelect && hasPlugins) { onSelect(searchApp) }
.enabled(hasPlugins), .enabled(hasPlugins),
headlineContent = { Text("Auto") }, headlineContent = { Text(stringResource(R.string.app_source_dialog_option_auto)) },
supportingContent = { Text(if (hasPlugins) "Use all installed downloaders to find a suitable app." else "No plugins available") }, supportingContent = {
Text(
if (hasPlugins)
stringResource(R.string.app_source_dialog_option_auto_description)
else
stringResource(R.string.app_source_dialog_option_auto_unavailable)
)
},
colors = transparentListItemColors colors = transparentListItemColors
) )
} }
@ -293,11 +300,17 @@ private fun AppSourceSelectorDialog(
item(key = "installed") { item(key = "installed") {
val (usable, text) = when { val (usable, text) = when {
// Mounted apps must be unpatched before patching, which cannot be done without root access. // Mounted apps must be unpatched before patching, which cannot be done without root access.
meta?.installType == InstallType.MOUNT && !hasRoot -> false to "Mounted apps cannot be patched again without root access" meta?.installType == InstallType.MOUNT && !hasRoot -> false to stringResource(
R.string.app_source_dialog_option_installed_no_root
)
// Patching already patched apps is not allowed because patches expect unpatched apps. // Patching already patched apps is not allowed because patches expect unpatched apps.
meta?.installType == InstallType.DEFAULT -> false to stringResource(R.string.already_patched) meta?.installType == InstallType.DEFAULT -> false to stringResource(R.string.already_patched)
// Version does not match suggested version. // Version does not match suggested version.
requiredVersion != null && app.version != requiredVersion -> false to "Version ${app.version} does not match the suggested version" requiredVersion != null && app.version != requiredVersion -> false to stringResource(
R.string.app_source_dialog_option_installed_version_not_suggested,
app.version
)
else -> true to app.version else -> true to app.version
} }
ListItem( ListItem(
@ -315,7 +328,6 @@ private fun AppSourceSelectorDialog(
ListItem( ListItem(
modifier = Modifier.clickable(enabled = canSelect) { onSelectPlugin(plugin) }, modifier = Modifier.clickable(enabled = canSelect) { onSelectPlugin(plugin) },
headlineContent = { Text(plugin.name) }, headlineContent = { Text(plugin.name) },
supportingContent = { Text("Try to find the app using ${plugin.name}") },
trailingContent = (@Composable { LoadingIndicator() }).takeIf { activeSearchJob == plugin.packageName }, trailingContent = (@Composable { LoadingIndicator() }).takeIf { activeSearchJob == plugin.packageName },
colors = transparentListItemColors colors = transparentListItemColors
) )

View File

@ -98,8 +98,8 @@ class PatcherViewModel(
var isInstalling by mutableStateOf(false) var isInstalling by mutableStateOf(false)
private set private set
private var currentActivityRequest: CompletableDeferred<Boolean>? by mutableStateOf(null) private var currentActivityRequest: Pair<CompletableDeferred<Boolean>, String>? by mutableStateOf(null)
val showActivityPromptDialog by derivedStateOf { currentActivityRequest != null } val activityPromptDialog by derivedStateOf { currentActivityRequest?.second }
private var launchedActivity: CompletableDeferred<ActivityResult>? = null private var launchedActivity: CompletableDeferred<ActivityResult>? = null
private val launchActivityChannel = Channel<Intent>() private val launchActivityChannel = Channel<Intent>()
@ -146,13 +146,13 @@ class PatcherViewModel(
downloadProgress, downloadProgress,
patchesProgress, patchesProgress,
setInputFile = { inputFile = it }, setInputFile = { inputFile = it },
handleStartActivityRequest = { intent -> handleStartActivityRequest = { plugin, intent ->
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
if (currentActivityRequest != null) throw Exception("Another request is already pending.") if (currentActivityRequest != null) throw Exception("Another request is already pending.")
try { try {
// Wait for the dialog interaction. // Wait for the dialog interaction.
val accepted = with(CompletableDeferred<Boolean>()) { val accepted = with(CompletableDeferred<Boolean>()) {
currentActivityRequest = this currentActivityRequest = this to plugin.name
await() await()
} }
@ -291,11 +291,11 @@ class PatcherViewModel(
fun isDeviceRooted() = rootInstaller.isDeviceRooted() fun isDeviceRooted() = rootInstaller.isDeviceRooted()
fun rejectInteraction() { fun rejectInteraction() {
currentActivityRequest?.complete(false) currentActivityRequest?.first?.complete(false)
} }
fun allowInteraction() { fun allowInteraction() {
currentActivityRequest?.complete(true) currentActivityRequest?.first?.complete(true)
} }
fun handleActivityResult(result: ActivityResult) { fun handleActivityResult(result: ActivityResult) {

View File

@ -36,6 +36,12 @@
<string name="bundle_name_fallback">Unnamed</string> <string name="bundle_name_fallback">Unnamed</string>
<string name="selected_app_meta_any_version">Any available version</string> <string name="selected_app_meta_any_version">Any available version</string>
<string name="app_source_dialog_title">Select source</string>
<string name="app_source_dialog_option_auto">Auto</string>
<string name="app_source_dialog_option_auto_description">Use all installed downloaders to find a suitable APK file</string>
<string name="app_source_dialog_option_auto_unavailable">No plugins available</string>
<string name="app_source_dialog_option_installed_no_root">Mounted apps cannot be patched again without root access</string>
<string name="app_source_dialog_option_installed_version_not_suggested">Version %s does not match the suggested version</string>
<string name="patch_item_description">Start patching the application</string> <string name="patch_item_description">Start patching the application</string>
<string name="patch_selector_item">Patch selection and options</string> <string name="patch_selector_item">Patch selection and options</string>
@ -275,6 +281,7 @@
<string name="save_apk_success">APK Saved</string> <string name="save_apk_success">APK Saved</string>
<string name="sign_fail">Failed to sign APK: %s</string> <string name="sign_fail">Failed to sign APK: %s</string>
<string name="save_logs">Save logs</string> <string name="save_logs">Save logs</string>
<string name="plugin_activity_dialog_body">User interaction is required in order to proceed with this plugin.</string>
<string name="select_install_type">Select installation type</string> <string name="select_install_type">Select installation type</string>
<string name="patcher_step_group_preparing">Preparing</string> <string name="patcher_step_group_preparing">Preparing</string>

View File

@ -5,11 +5,13 @@ import android.os.Bundle
import android.os.IBinder import android.os.IBinder
import android.os.Parcelable import android.os.Parcelable
import android.view.MenuItem import android.view.MenuItem
import android.view.MotionEvent
import android.webkit.CookieManager import android.webkit.CookieManager
import android.webkit.WebSettings import android.webkit.WebSettings
import android.webkit.WebView import android.webkit.WebView
import android.webkit.WebViewClient import android.webkit.WebViewClient
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.addCallback
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
@ -31,14 +33,20 @@ class WebViewActivity : ComponentActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val vm by viewModels<WebViewModel>() val vm by viewModels<WebViewModel>()
enableEdgeToEdge() enableEdgeToEdge()
setContentView(R.layout.activity_webview) setContentView(R.layout.activity_webview)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets insets
} }
val webView = findViewById<WebView>(R.id.webview)
onBackPressedDispatcher.addCallback {
if (webView.canGoBack()) webView.goBack()
else cancelActivity()
}
val params = intent.getParcelableExtra<Parameters>(KEY)!! val params = intent.getParcelableExtra<Parameters>(KEY)!!
actionBar?.apply { actionBar?.apply {
title = params.title title = params.title
@ -49,12 +57,11 @@ class WebViewActivity : ComponentActivity() {
val events = IWebViewEvents.Stub.asInterface(params.events)!! val events = IWebViewEvents.Stub.asInterface(params.events)!!
vm.setup(events) vm.setup(events)
val webView = findViewById<WebView>(R.id.content).apply { webView.apply {
settings.apply { settings.apply {
cacheMode = WebSettings.LOAD_NO_CACHE cacheMode = WebSettings.LOAD_NO_CACHE
databaseEnabled = false
allowContentAccess = false allowContentAccess = false
domStorageEnabled = false domStorageEnabled = true
javaScriptEnabled = true javaScriptEnabled = true
} }
@ -80,9 +87,14 @@ class WebViewActivity : ComponentActivity() {
} }
} }
override fun onOptionsItemSelected(item: MenuItem) = if (item.itemId == android.R.id.home) { private fun cancelActivity() {
setResult(RESULT_CANCELED) setResult(RESULT_CANCELED)
finish() finish()
}
override fun onOptionsItemSelected(item: MenuItem) = if (item.itemId == android.R.id.home) {
cancelActivity()
true true
} else super.onOptionsItemSelected(item) } else super.onOptionsItemSelected(item)

View File

@ -1,16 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".webview.WebViewActivity"> android:id="@+id/main">
<WebView <WebView
android:id="@+id/content" android:id="@+id/webview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent" />
tools:layout_editor_absoluteX="1dp"
tools:layout_editor_absoluteY="1dp" />
</LinearLayout> </LinearLayout>

View File

@ -12,18 +12,6 @@ import app.revanced.manager.plugin.downloader.webview.WebViewDownloader
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import kotlin.io.path.* import kotlin.io.path.*
// TODO: update UI error presentation and strings
@Parcelize
class InstalledApp(val path: String) : Parcelable
private val application by lazy {
// Don't do this in a real plugin.
val clazz = Class.forName("android.app.ActivityThread")
val activityThread = clazz.getMethod("currentActivityThread")(null)
clazz.getMethod("getApplication")(activityThread) as Application
}
val apkMirrorDownloader = WebViewDownloader { packageName, version -> val apkMirrorDownloader = WebViewDownloader { packageName, version ->
with(Uri.Builder()) { with(Uri.Builder()) {
scheme("https") scheme("https")
@ -41,6 +29,16 @@ val apkMirrorDownloader = WebViewDownloader { packageName, version ->
} }
} }
@Parcelize
class InstalledApp(val path: String) : Parcelable
private val application by lazy {
// Don't do this in a real plugin.
val clazz = Class.forName("android.app.ActivityThread")
val activityThread = clazz.getMethod("currentActivityThread")(null)
clazz.getMethod("getApplication")(activityThread) as Application
}
val installedAppDownloader = Downloader<InstalledApp> { val installedAppDownloader = Downloader<InstalledApp> {
val pm = application.packageManager val pm = application.packageManager

View File

@ -1,14 +1,14 @@
[versions] [versions]
ktx = "1.15.0" ktx = "1.15.0"
material3 = "1.3.1" material3 = "1.3.1"
ui-tooling = "1.7.5" ui-tooling = "1.7.6"
viewmodel-lifecycle = "2.8.7" viewmodel-lifecycle = "2.8.7"
splash-screen = "1.0.1" splash-screen = "1.0.1"
activity = "1.9.3" activity = "1.9.3"
appcompat = "1.7.0" appcompat = "1.7.0"
preferences-datastore = "1.1.1" preferences-datastore = "1.1.1"
work-runtime = "2.10.0" work-runtime = "2.10.0"
compose-bom = "2024.11.00" compose-bom = "2024.12.01"
accompanist = "0.34.0" accompanist = "0.34.0"
placeholder = "1.1.2" placeholder = "1.1.2"
reorderable = "1.5.2" reorderable = "1.5.2"
@ -24,9 +24,9 @@ reimagined-navigation = "1.5.0"
ktor = "2.3.9" ktor = "2.3.9"
markdown-renderer = "0.22.0" markdown-renderer = "0.22.0"
fading-edges = "1.0.4" fading-edges = "1.0.4"
kotlin = "2.0.21" kotlin = "2.1.0"
android-gradle-plugin = "8.7.2" android-gradle-plugin = "8.7.3"
dev-tools-gradle-plugin = "2.0.21-1.0.27" dev-tools-gradle-plugin = "2.1.0-1.0.29"
about-libraries-gradle-plugin = "11.1.1" about-libraries-gradle-plugin = "11.1.1"
binary-compatibility-validator = "0.15.1" binary-compatibility-validator = "0.15.1"
coil = "2.6.0" coil = "2.6.0"