diff --git a/app/src/main/java/app/revanced/manager/patcher/worker/PatcherWorker.kt b/app/src/main/java/app/revanced/manager/patcher/worker/PatcherWorker.kt index a7f0bfce..f7e6f815 100644 --- a/app/src/main/java/app/revanced/manager/patcher/worker/PatcherWorker.kt +++ b/app/src/main/java/app/revanced/manager/patcher/worker/PatcherWorker.kt @@ -76,7 +76,7 @@ class PatcherWorker( val logger: Logger, val downloadProgress: MutableStateFlow?>, val patchesProgress: MutableStateFlow>, - val handleStartActivityRequest: suspend (Intent) -> ActivityResult, + val handleStartActivityRequest: suspend (LoadedDownloaderPlugin, Intent) -> ActivityResult, val setInputFile: (File) -> Unit, val onProgress: ProgressEventHandler ) { @@ -182,7 +182,7 @@ class PatcherWorker( override val pluginPackageName = plugin.packageName override val hostPackageName = applicationContext.packageName override suspend fun requestStartActivity(intent: Intent): Intent? { - val result = args.handleStartActivityRequest(intent) + val result = args.handleStartActivityRequest(plugin, intent) return when (result.resultCode) { Activity.RESULT_OK -> result.data Activity.RESULT_CANCELED -> throw UserInteractionException.Activity.Cancelled() diff --git a/app/src/main/java/app/revanced/manager/ui/screen/PatcherScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/PatcherScreen.kt index 668bd1e3..5df81ed3 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/PatcherScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/PatcherScreen.kt @@ -96,7 +96,7 @@ fun PatcherScreen( activityLauncher.launch(intent) } - if (vm.showActivityPromptDialog) + vm.activityPromptDialog?.let { title -> AlertDialog( onDismissRequest = vm::rejectInteraction, confirmButton = { @@ -113,11 +113,12 @@ fun PatcherScreen( Text(stringResource(R.string.cancel)) } }, - title = { Text("User interaction required.") }, + title = { Text(title) }, text = { - Text("User interaction is required to proceed.") + Text(stringResource(R.string.plugin_activity_dialog_body)) } ) + } AppScaffold( topBar = { diff --git a/app/src/main/java/app/revanced/manager/ui/screen/SelectedAppInfoScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/SelectedAppInfoScreen.kt index c4d2633b..5c9acdeb 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/SelectedAppInfoScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/SelectedAppInfoScreen.kt @@ -273,7 +273,7 @@ private fun AppSourceSelectorDialog( Text(stringResource(R.string.cancel)) } }, - title = { Text("Select source") }, + title = { Text(stringResource(R.string.app_source_dialog_title)) }, textHorizontalPadding = PaddingValues(horizontal = 0.dp), text = { LazyColumn { @@ -283,8 +283,15 @@ private fun AppSourceSelectorDialog( modifier = Modifier .clickable(enabled = canSelect && hasPlugins) { onSelect(searchApp) } .enabled(hasPlugins), - headlineContent = { Text("Auto") }, - supportingContent = { Text(if (hasPlugins) "Use all installed downloaders to find a suitable app." else "No plugins available") }, + headlineContent = { Text(stringResource(R.string.app_source_dialog_option_auto)) }, + 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 ) } @@ -293,11 +300,17 @@ private fun AppSourceSelectorDialog( item(key = "installed") { val (usable, text) = when { // 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. meta?.installType == InstallType.DEFAULT -> false to stringResource(R.string.already_patched) // 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 } ListItem( @@ -315,7 +328,6 @@ private fun AppSourceSelectorDialog( ListItem( modifier = Modifier.clickable(enabled = canSelect) { onSelectPlugin(plugin) }, headlineContent = { Text(plugin.name) }, - supportingContent = { Text("Try to find the app using ${plugin.name}") }, trailingContent = (@Composable { LoadingIndicator() }).takeIf { activeSearchJob == plugin.packageName }, colors = transparentListItemColors ) diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/PatcherViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/PatcherViewModel.kt index 8ef24db1..93c2d481 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/PatcherViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/PatcherViewModel.kt @@ -98,8 +98,8 @@ class PatcherViewModel( var isInstalling by mutableStateOf(false) private set - private var currentActivityRequest: CompletableDeferred? by mutableStateOf(null) - val showActivityPromptDialog by derivedStateOf { currentActivityRequest != null } + private var currentActivityRequest: Pair, String>? by mutableStateOf(null) + val activityPromptDialog by derivedStateOf { currentActivityRequest?.second } private var launchedActivity: CompletableDeferred? = null private val launchActivityChannel = Channel() @@ -146,13 +146,13 @@ class PatcherViewModel( downloadProgress, patchesProgress, setInputFile = { inputFile = it }, - handleStartActivityRequest = { intent -> + handleStartActivityRequest = { plugin, intent -> withContext(Dispatchers.Main) { if (currentActivityRequest != null) throw Exception("Another request is already pending.") try { // Wait for the dialog interaction. val accepted = with(CompletableDeferred()) { - currentActivityRequest = this + currentActivityRequest = this to plugin.name await() } @@ -291,11 +291,11 @@ class PatcherViewModel( fun isDeviceRooted() = rootInstaller.isDeviceRooted() fun rejectInteraction() { - currentActivityRequest?.complete(false) + currentActivityRequest?.first?.complete(false) } fun allowInteraction() { - currentActivityRequest?.complete(true) + currentActivityRequest?.first?.complete(true) } fun handleActivityResult(result: ActivityResult) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5791d039..966e37cd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -36,6 +36,12 @@ Unnamed Any available version + Select source + Auto + Use all installed downloaders to find a suitable APK file + No plugins available + Mounted apps cannot be patched again without root access + Version %s does not match the suggested version Start patching the application Patch selection and options @@ -275,6 +281,7 @@ APK Saved Failed to sign APK: %s Save logs + User interaction is required in order to proceed with this plugin. Select installation type Preparing diff --git a/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/webview/WebViewActivity.kt b/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/webview/WebViewActivity.kt index 7c0796f8..0c9cfe38 100644 --- a/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/webview/WebViewActivity.kt +++ b/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/webview/WebViewActivity.kt @@ -5,11 +5,13 @@ import android.os.Bundle import android.os.IBinder import android.os.Parcelable import android.view.MenuItem +import android.view.MotionEvent import android.webkit.CookieManager import android.webkit.WebSettings import android.webkit.WebView import android.webkit.WebViewClient import androidx.activity.ComponentActivity +import androidx.activity.addCallback import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels import androidx.core.view.ViewCompat @@ -31,14 +33,20 @@ class WebViewActivity : ComponentActivity() { super.onCreate(savedInstanceState) val vm by viewModels() - enableEdgeToEdge() setContentView(R.layout.activity_webview) + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) insets } + val webView = findViewById(R.id.webview) + onBackPressedDispatcher.addCallback { + if (webView.canGoBack()) webView.goBack() + else cancelActivity() + } + val params = intent.getParcelableExtra(KEY)!! actionBar?.apply { title = params.title @@ -49,12 +57,11 @@ class WebViewActivity : ComponentActivity() { val events = IWebViewEvents.Stub.asInterface(params.events)!! vm.setup(events) - val webView = findViewById(R.id.content).apply { + webView.apply { settings.apply { cacheMode = WebSettings.LOAD_NO_CACHE - databaseEnabled = false allowContentAccess = false - domStorageEnabled = false + domStorageEnabled = 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) finish() + } + + override fun onOptionsItemSelected(item: MenuItem) = if (item.itemId == android.R.id.home) { + cancelActivity() + true } else super.onOptionsItemSelected(item) diff --git a/downloader-plugin/src/main/res/layout/activity_webview.xml b/downloader-plugin/src/main/res/layout/activity_webview.xml index 466721cc..51f761d9 100644 --- a/downloader-plugin/src/main/res/layout/activity_webview.xml +++ b/downloader-plugin/src/main/res/layout/activity_webview.xml @@ -1,16 +1,11 @@ + android:id="@+id/main"> + android:layout_height="match_parent" /> \ No newline at end of file diff --git a/example-downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/example/ExamplePlugin.kt b/example-downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/example/ExamplePlugin.kt index 8d0f0cb6..dd2b26c5 100644 --- a/example-downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/example/ExamplePlugin.kt +++ b/example-downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/example/ExamplePlugin.kt @@ -12,18 +12,6 @@ import app.revanced.manager.plugin.downloader.webview.WebViewDownloader import kotlinx.parcelize.Parcelize 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 -> with(Uri.Builder()) { 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 { val pm = application.packageManager @@ -68,4 +66,4 @@ val installedAppDownloader = Downloader { reportSize(path.fileSize()) Files.copy(path, outputStream) }*/ -} \ No newline at end of file +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 27b30b6d..ab9e5bc8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,14 +1,14 @@ [versions] ktx = "1.15.0" material3 = "1.3.1" -ui-tooling = "1.7.5" +ui-tooling = "1.7.6" viewmodel-lifecycle = "2.8.7" splash-screen = "1.0.1" activity = "1.9.3" appcompat = "1.7.0" preferences-datastore = "1.1.1" work-runtime = "2.10.0" -compose-bom = "2024.11.00" +compose-bom = "2024.12.01" accompanist = "0.34.0" placeholder = "1.1.2" reorderable = "1.5.2" @@ -24,9 +24,9 @@ reimagined-navigation = "1.5.0" ktor = "2.3.9" markdown-renderer = "0.22.0" fading-edges = "1.0.4" -kotlin = "2.0.21" -android-gradle-plugin = "8.7.2" -dev-tools-gradle-plugin = "2.0.21-1.0.27" +kotlin = "2.1.0" +android-gradle-plugin = "8.7.3" +dev-tools-gradle-plugin = "2.1.0-1.0.29" about-libraries-gradle-plugin = "11.1.1" binary-compatibility-validator = "0.15.1" coil = "2.6.0"