diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 602582fac..2ae18e4ef 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -271,7 +271,8 @@ dependencies { implementation(libs.square.moshi) ksp(libs.square.moshi.kotlin) - implementation("dev.mmrlx:terminal:1.0.3") + implementation(libs.mmrlx.terminal) + implementation(libs.mmrlx.webui.core) implementation("dev.chrisbanes.haze:haze:1.6.10") implementation("dev.chrisbanes.haze:haze-materials:1.6.10") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e45ad9e76..b86633827 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,6 +10,8 @@ + diff --git a/app/src/main/assets/markdown.html b/app/src/main/assets/markdown.html index 86ebf3b13..31ef35fd7 100644 --- a/app/src/main/assets/markdown.html +++ b/app/src/main/assets/markdown.html @@ -11,19 +11,21 @@ -
-
-
- +
+
+
+ \ No newline at end of file diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/pathHandler/AssetsPathHandler.kt b/app/src/main/kotlin/com/dergoogler/mmrl/pathHandler/AssetsPathHandler.kt index 26607c11a..b8255883f 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/pathHandler/AssetsPathHandler.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/pathHandler/AssetsPathHandler.kt @@ -1,24 +1,19 @@ package com.dergoogler.mmrl.pathHandler -import android.annotation.SuppressLint -import android.content.Context import android.util.Log import android.webkit.WebResourceResponse import com.dergoogler.mmrl.hybridwebui.HybridWebUI -import com.dergoogler.mmrl.hybridwebui.HybridWebUIResourceRequest +import dev.mmrlx.webui.PathHandler +import dev.mmrlx.webui.WebUI +import dev.mmrlx.webui.WebUIResourceRequest import java.io.IOException class AssetsPathHandler( - private val context: Context -) : HybridWebUI.PathHandler() { + webui: WebUI, +) : PathHandler(webui) { + private val assetHelper get() = kontext.assets - private val assetHelper get() = context.assets - - @SuppressLint("WrongThread") - override fun handle( - view: HybridWebUI, - request: HybridWebUIResourceRequest, - ): WebResourceResponse { + override fun handle(request: WebUIResourceRequest): WebResourceResponse { val path = request.path try { diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/pathHandler/InternalPathHandler.kt b/app/src/main/kotlin/com/dergoogler/mmrl/pathHandler/InternalPathHandler.kt index f0d9b8c88..c1e90cad5 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/pathHandler/InternalPathHandler.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/pathHandler/InternalPathHandler.kt @@ -1,46 +1,52 @@ package com.dergoogler.mmrl.pathHandler -import android.content.Context import android.util.Log import android.webkit.WebResourceResponse import androidx.compose.material3.ColorScheme -import com.dergoogler.mmrl.hybridwebui.HybridWebUI -import com.dergoogler.mmrl.hybridwebui.HybridWebUIInsets -import com.dergoogler.mmrl.hybridwebui.HybridWebUIResourceRequest import com.dergoogler.mmrl.model.WebColors +import dev.mmrlx.webui.PathHandler +import dev.mmrlx.webui.WebUI +import dev.mmrlx.webui.WebUIResourceRequest import java.io.IOException +import java.net.HttpURLConnection +import java.net.URL class InternalPathHandler( - private val context: Context, + webui: WebUI, + private val readmeUrl: String, private val colorScheme: ColorScheme, - private val insets: HybridWebUIInsets, -) : HybridWebUI.PathHandler() { +) : PathHandler(webui) { + override val id = "/internal/" val webColors get() = WebColors(colorScheme) - val assetsPathHandler = AssetsPathHandler(context) + val assetsPathHandler = AssetsPathHandler(this) - override fun handle( - view: HybridWebUI, - request: HybridWebUIResourceRequest, - ): WebResourceResponse { + override fun handle(request: WebUIResourceRequest): WebResourceResponse { val path = request.path try { if (path.matches(Regex("^assets(/.*)?$"))) { return assetsPathHandler.handle( - view, - HybridWebUIResourceRequest( + WebUIResourceRequest( method = request.method, isForMainFrame = request.isForMainFrame, url = request.url, path = path.removePrefix("assets/"), requestHeaders = request.requestHeaders, isRedirect = request.isRedirect, - hasGesture = request.hasGesture + hasGesture = request.hasGesture() ) ) } + + + if (path.matches(Regex("readme\\.md"))) { + val connection = URL(readmeUrl).openConnection() as HttpURLConnection + connection.connect() + return htmlResponse(connection.getInputStream()) + } + if (path.matches(Regex("insets\\.css"))) { return insets.css.asStyleResponse() } diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/ViewDescriptionScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/ViewDescriptionScreen.kt index 25224fcb0..541a54de2 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/ViewDescriptionScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/ViewDescriptionScreen.kt @@ -1,49 +1,29 @@ package com.dergoogler.mmrl.ui.screens.moduleView import android.annotation.SuppressLint -import android.content.Intent -import android.net.Uri -import android.util.Log -import android.webkit.JavascriptInterface -import android.webkit.WebResourceRequest -import android.webkit.WebView -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.ColorScheme import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.viewinterop.AndroidView +import com.dergoogler.mmrl.BuildConfig import com.dergoogler.mmrl.R -import com.dergoogler.mmrl.app.Event.Companion.isFailed -import com.dergoogler.mmrl.app.Event.Companion.isLoading -import com.dergoogler.mmrl.app.Event.Companion.isSucceeded import com.dergoogler.mmrl.ext.none -import com.dergoogler.mmrl.hybridwebui.HybridWebUI -import com.dergoogler.mmrl.hybridwebui.HybridWebUIClient -import com.dergoogler.mmrl.hybridwebui.HybridWebUIInsets -import com.dergoogler.mmrl.network.compose.requestString import com.dergoogler.mmrl.pathHandler.InternalPathHandler -import com.dergoogler.mmrl.ui.component.Failed -import com.dergoogler.mmrl.ui.component.Loading import com.dergoogler.mmrl.ui.component.LocalScreenProvider import com.dergoogler.mmrl.ui.component.scaffold.Scaffold import com.dergoogler.mmrl.ui.component.toolbar.BlurToolbar @@ -55,6 +35,9 @@ import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.navigation.DestinationsNavigator import dev.chrisbanes.haze.hazeSource +import dev.mmrlx.compose.webui.WebUIView +import dev.mmrlx.compose.webui.insets +import dev.mmrlx.compose.webui.rememberWebUIState const val launchUrl = "https://mui.kernelsu.org/internal/assets/markdown.html" @@ -67,21 +50,40 @@ fun ViewDescriptionScreen(readmeUrl: String) = val navigator = LocalDestinationsNavigator.current val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() val userPrefs = LocalUserPreferences.current - - var hw by remember { mutableStateOf(null) } - - var readme by remember { mutableStateOf("") } - val event = - requestString( - url = readmeUrl, - onSuccess = { readme = it }, - ) - - val webColors = colorScheme + val scheme = MaterialTheme.colorScheme + + @Suppress("KotlinConstantConditions") val wstate = + rememberWebUIState("https://desc.mmrl.dev", "internal/assets/markdown.html") { + it + .settings { + schemeWhitelist += "ksu" + useDefaultApplicationInterface = false + useDefaultFileSystem = false + debug = BuildConfig.DEBUG + forceKillProcess = false + useConsoleInterceptor = false + darkMode = userPrefs.isDarkMode() + } + .client { + // not related to client + it.webview.setBackgroundColor(scheme.background.toArgb()) + } + .chromeClient { } + .registerPathHandler( + InternalPathHandler::class.java + ) { + add( + String::class.java to readmeUrl + ) + add( + ColorScheme::class.java to scheme + ) + } + } DisposableEffect(Unit) { onDispose { - hw?.destroy() + wstate.webui.destroy() } } @@ -96,6 +98,12 @@ fun ViewDescriptionScreen(readmeUrl: String) = contentWindowInsets = WindowInsets.none, ) { innerPadding -> val bottomBarPaddingValues = LocalMainScreenInnerPaddings.current + wstate.webui.insets( + PaddingValues( + top = innerPadding.calculateTopPadding(), + bottom = bottomBarPaddingValues.calculateBottomPadding(), + ) + ) Column( modifier = @@ -103,109 +111,8 @@ fun ViewDescriptionScreen(readmeUrl: String) = .fillMaxSize() .hazeSource(LocalHazeState.current), ) { - AnimatedVisibility( - modifier = Modifier.fillMaxSize(), - visible = event.isLoading, - enter = fadeIn(), - exit = fadeOut(), - ) { - Loading() - } - - AnimatedVisibility( - visible = event.isFailed, - enter = fadeIn(), - exit = fadeOut(), - ) { - Failed() - } - - AnimatedVisibility( - visible = event.isSucceeded, - enter = fadeIn(), - exit = fadeOut(), - ) { - this@Scaffold.ResponsiveContent { - AndroidView( - factory = { context -> - // #NotAReadlDomain - val wv = HybridWebUI(context, "https://desc.mmrl.dev").apply { - setBackgroundColor(webColors.background.toArgb()) - addPathHandler( - "/internal/", - InternalPathHandler( - context, - webColors, - HybridWebUIInsets( - top = - with(density) { - val pad = - innerPadding.calculateTopPadding() - val px = - with(density) { pad.toPx() }.toInt() - (px / this.density).toInt() - }, - bottom = - with(density) { - val pad = - bottomBarPaddingValues.calculateBottomPadding() - val px = - with(density) { pad.toPx() }.toInt() - (px / this.density).toInt() - }, - left = 0, - right = 0, - ) - ) - ) - - webViewClient = object : HybridWebUIClient() { - override fun shouldOverrideUrlLoading( - view: WebView, - request: WebResourceRequest, - ): Boolean { - val mUri = request.url ?: return false - val mUrl = mUri.toString() - - val isLoadedData = mUrl.startsWith("data:") - val isUnsafe = !Regex("^https?://desc\\.mmrl\\.dev(/.*)?$").matches(mUrl) - - if (isLoadedData) { - return false - } - - if (isUnsafe) { - openUri(mUri) - return true - } - - view.loadUrl(mUrl) - return false - } - - private fun openUri(uri: Uri) { - try { - val intent = Intent(Intent.ACTION_VIEW, uri) - context.startActivity(intent) - } catch (e: Exception) { - Log.e("ViewDescriptionScreen", "Error opening URI: $uri", e) - } - } - } - - addJavascriptInterface(object { - @JavascriptInterface - fun get() = readme - }, "markdown") - - loadPage("/internal/assets/markdown.html") - } - - hw = wv - wv - }, - ) - } + this@Scaffold.ResponsiveContent { + WebUIView(wstate) } } } @@ -226,6 +133,4 @@ private fun TopBar( }, title = { Text(text = stringResource(id = R.string.view_module_about_this_module)) }, scrollBehavior = scrollBehavior, -) - -fun HybridWebUI.loadPage(path: String) = loadUrl("$uri$path") \ No newline at end of file +) \ No newline at end of file diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/utils/PackageManager.kt b/app/src/main/kotlin/com/dergoogler/mmrl/utils/PackageManager.kt new file mode 100644 index 000000000..4e93742a5 --- /dev/null +++ b/app/src/main/kotlin/com/dergoogler/mmrl/utils/PackageManager.kt @@ -0,0 +1,46 @@ +package com.dergoogler.mmrl.utils + +import android.content.Context +import android.content.Intent +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.content.pm.ResolveInfo +import android.os.Build +import com.dergoogler.mmrl.BuildConfig + +private fun PackageManager.getInstalledPackagesCompat(): List { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + getInstalledPackages( + PackageManager.PackageInfoFlags.of(0) + ) + } else { + @Suppress("DEPRECATION") + getInstalledPackages(0) + } +} + +private fun PackageManager.getLaunchableApps(): List { + val intent = Intent(Intent.ACTION_MAIN).apply { + addCategory(Intent.CATEGORY_LAUNCHER) + } + + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + queryIntentActivities( + intent, + PackageManager.ResolveInfoFlags.of(0) + ) + } else { + @Suppress("DEPRECATION") + queryIntentActivities(intent, 0) + } +} + +val Context.packages: List + get() { + if (BuildConfig.IS_GOOGLE_PLAY_BUILD) { + return packageManager.getLaunchableApps() + .map { packageManager.getPackageInfo(it.activityInfo.packageName, 0) } + } + + return packageManager.getInstalledPackagesCompat() + } \ No newline at end of file diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/viewmodel/SuperUserViewModel.kt b/app/src/main/kotlin/com/dergoogler/mmrl/viewmodel/SuperUserViewModel.kt index 8fdfb98e0..25dd5f0eb 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/viewmodel/SuperUserViewModel.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/viewmodel/SuperUserViewModel.kt @@ -24,6 +24,7 @@ import com.dergoogler.mmrl.platform.ksu.KsuNative import com.dergoogler.mmrl.platform.ksu.Profile import com.dergoogler.mmrl.repository.LocalRepository import com.dergoogler.mmrl.repository.ModulesRepository +import com.dergoogler.mmrl.utils.packages import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow @@ -186,8 +187,7 @@ class SuperUserViewModel private fun getAppList(): List { val pm = context.packageManager - val packages = - PlatformManager.packageManager.getInstalledPackagesAll(PlatformManager.userManager, 0) + val packages = context.packages return packages .map { pkg -> val appInfo = pkg.applicationInfo!! diff --git a/app/src/playstore/AndroidManifest.xml b/app/src/playstore/AndroidManifest.xml new file mode 100644 index 000000000..4900bfa09 --- /dev/null +++ b/app/src/playstore/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b4e31ec57..cbea7f441 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -41,6 +41,7 @@ squareRetrofit = "2.11.0" squareOkhttp = "4.12.0" squareMoshi = "1.15.1" runtimeAndroid = "1.7.6" +mmrlx = "1.0.145" webuiXPortable = "c92f5f6c3c" workRuntimeKtx = "2.10.0" lifecycleProcess = "2.8.7" @@ -136,6 +137,8 @@ dev-rikka-rikkax-parcelablelist = { module = "dev.rikka.rikkax.parcelablelist:pa markwon-core = "io.noties.markwon:core:4.6.2" hiddenApiBypass = "org.lsposed.hiddenapibypass:hiddenapibypass:6.1" +mmrlx-terminal = { module = "dev.mmrlx:terminal", version.ref = "mmrlx" } +mmrlx-webui-core = { module = "dev.mmrlx:webui.core", version.ref = "mmrlx" } timber = "com.jakewharton.timber:timber:5.0.1" # Dependencies of the included build-logic