mirror of
https://github.com/revanced/revanced-manager.git
synced 2025-05-03 07:14:25 +02:00
add webview api
This commit is contained in:
parent
3029a61e99
commit
66c06a6fe5
@ -113,9 +113,10 @@ dependencies {
|
|||||||
implementation(libs.runtime.ktx)
|
implementation(libs.runtime.ktx)
|
||||||
implementation(libs.runtime.compose)
|
implementation(libs.runtime.compose)
|
||||||
implementation(libs.splash.screen)
|
implementation(libs.splash.screen)
|
||||||
implementation(libs.compose.activity)
|
implementation(libs.activity.compose)
|
||||||
implementation(libs.work.runtime.ktx)
|
implementation(libs.work.runtime.ktx)
|
||||||
implementation(libs.preferences.datastore)
|
implementation(libs.preferences.datastore)
|
||||||
|
implementation(libs.appcompat)
|
||||||
|
|
||||||
// Compose
|
// Compose
|
||||||
implementation(platform(libs.compose.bom))
|
implementation(platform(libs.compose.bom))
|
||||||
|
@ -48,6 +48,8 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".plugin.downloader.webview.WebViewActivity" android:exported="false" android:theme="@style/Theme.WebViewActivity" />
|
||||||
|
|
||||||
<service android:name=".service.InstallService" />
|
<service android:name=".service.InstallService" />
|
||||||
<service android:name=".service.UninstallService" />
|
<service android:name=".service.UninstallService" />
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ package app.revanced.manager
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@ -36,7 +37,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
|
enableEdgeToEdge()
|
||||||
installSplashScreen()
|
installSplashScreen()
|
||||||
|
|
||||||
val vm: MainViewModel = getAndroidViewModel()
|
val vm: MainViewModel = getAndroidViewModel()
|
||||||
|
@ -215,6 +215,8 @@ class SelectedAppInfoViewModel(input: Params) : ViewModel(), KoinComponent {
|
|||||||
ParceledDownloaderData(plugin, data)
|
ParceledDownloaderData(plugin, data)
|
||||||
)
|
)
|
||||||
} ?: app.toast("App was not found")
|
} ?: app.toast("App was not found")
|
||||||
|
} catch (e: UserInteractionException.Activity) {
|
||||||
|
app.toast(e.message!!)
|
||||||
} finally {
|
} finally {
|
||||||
pluginAction = null
|
pluginAction = null
|
||||||
dismissSourceSelector()
|
dismissSourceSelector()
|
||||||
|
@ -4,5 +4,7 @@
|
|||||||
<style name="Theme.ReVancedManager" parent="Theme.SplashScreen">
|
<style name="Theme.ReVancedManager" parent="Theme.SplashScreen">
|
||||||
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_launcher_foreground</item>
|
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_launcher_foreground</item>
|
||||||
<item name="postSplashScreenTheme">@style/Theme.AppCompat.NoActionBar</item>
|
<item name="postSplashScreenTheme">@style/Theme.AppCompat.NoActionBar</item>
|
||||||
|
<item name="android:windowActionBar">false</item>
|
||||||
|
<item name="android:windowNoTitle">true</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
@ -31,12 +31,15 @@ android {
|
|||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "17"
|
jvmTarget = "17"
|
||||||
}
|
}
|
||||||
|
buildFeatures {
|
||||||
|
aidl = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.androidx.appcompat)
|
implementation(libs.androidx.ktx)
|
||||||
implementation(libs.material)
|
implementation(libs.activity.ktx)
|
||||||
implementation(libs.androidx.activity)
|
implementation(libs.runtime.ktx)
|
||||||
implementation(libs.androidx.constraintlayout)
|
implementation(libs.appcompat)
|
||||||
}
|
}
|
||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
// IWebView.aidl
|
||||||
|
package app.revanced.manager.plugin.downloader.webview;
|
||||||
|
|
||||||
|
oneway interface IWebView {
|
||||||
|
void load(String url);
|
||||||
|
void finish();
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
// IWebViewEvents.aidl
|
||||||
|
package app.revanced.manager.plugin.downloader.webview;
|
||||||
|
|
||||||
|
import app.revanced.manager.plugin.downloader.webview.IWebView;
|
||||||
|
|
||||||
|
oneway interface IWebViewEvents {
|
||||||
|
void ready(IWebView iface);
|
||||||
|
void pageLoad(String url);
|
||||||
|
void download(String url, String mimetype, String userAgent);
|
||||||
|
}
|
@ -0,0 +1,126 @@
|
|||||||
|
package app.revanced.manager.plugin.downloader.webview
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
|
import app.revanced.manager.plugin.downloader.DownloaderScope
|
||||||
|
import app.revanced.manager.plugin.downloader.GetResult
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
internal typealias PageLoadCallback<T> = suspend WebViewCallbackScope<T>.(url: String) -> Unit
|
||||||
|
internal typealias DownloadCallback<T> = suspend WebViewCallbackScope<T>.(url: String, mimeType: String, userAgent: String) -> Unit
|
||||||
|
internal typealias ReadyCallback<T> = suspend WebViewCallbackScope<T>.() -> Unit
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class DownloadUrl(val url: String, val mimeType: String, val userAgent: String) : Parcelable {
|
||||||
|
fun toResult() = with(URI.create(url).toURL().openConnection() as HttpURLConnection) {
|
||||||
|
useCaches = false
|
||||||
|
allowUserInteraction = false
|
||||||
|
setRequestProperty("User-Agent", userAgent)
|
||||||
|
connectTimeout = 10_000
|
||||||
|
connect()
|
||||||
|
inputStream to getHeaderField("Content-Length").toLong()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WebViewCallbackScope<T : Parcelable> {
|
||||||
|
suspend fun finish(result: GetResult<T>?)
|
||||||
|
suspend fun load(url: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebViewScope<T : Parcelable> internal constructor(
|
||||||
|
coroutineScope: CoroutineScope,
|
||||||
|
setResult: (GetResult<T>?) -> Unit
|
||||||
|
) {
|
||||||
|
private var onPageLoadCallback: PageLoadCallback<T> = {}
|
||||||
|
private var onDownloadCallback: DownloadCallback<T> = { _, _, _ -> }
|
||||||
|
private var onReadyCallback: ReadyCallback<T> =
|
||||||
|
{ throw Exception("Ready callback not set") }
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
private val dispatcher = Dispatchers.Default.limitedParallelism(1)
|
||||||
|
private var current: IWebView? = null
|
||||||
|
private val webView: IWebView
|
||||||
|
inline get() = current ?: throw Exception("WebView interface unavailable")
|
||||||
|
|
||||||
|
internal val binder = object : IWebViewEvents.Stub() {
|
||||||
|
override fun ready(iface: IWebView?) {
|
||||||
|
coroutineScope.launch(dispatcher) {
|
||||||
|
val wasNull = current == null
|
||||||
|
current = iface
|
||||||
|
if (wasNull) onReadyCallback(callbackScope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageLoad(url: String?) {
|
||||||
|
coroutineScope.launch(dispatcher) { onPageLoadCallback(callbackScope, url!!) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun download(url: String?, mimetype: String?, userAgent: String?) {
|
||||||
|
coroutineScope.launch(dispatcher) {
|
||||||
|
onDownloadCallback(
|
||||||
|
callbackScope,
|
||||||
|
url!!,
|
||||||
|
mimetype!!,
|
||||||
|
userAgent!!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val callbackScope = object : WebViewCallbackScope<T> {
|
||||||
|
override suspend fun finish(result: GetResult<T>?) {
|
||||||
|
setResult(result)
|
||||||
|
// Tell the WebViewActivity to finish
|
||||||
|
webView.let { withContext(Dispatchers.IO) { it.finish() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun load(url: String) {
|
||||||
|
webView.let { withContext(Dispatchers.IO) { it.load(url) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onDownload(block: DownloadCallback<T>) {
|
||||||
|
onDownloadCallback = block
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onPageLoad(block: PageLoadCallback<T>) {
|
||||||
|
onPageLoadCallback = block
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onReady(block: ReadyCallback<T>) {
|
||||||
|
onReadyCallback = block
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T : Parcelable> DownloaderScope<T>.webView(block: WebViewScope<T>.(packageName: String, version: String?) -> Unit) =
|
||||||
|
get { pkgName, version ->
|
||||||
|
var result: GetResult<T>? = null
|
||||||
|
|
||||||
|
coroutineScope {
|
||||||
|
val scope = WebViewScope(this) { result = it }
|
||||||
|
scope.block(pkgName, version)
|
||||||
|
requestStartActivity(Intent().apply {
|
||||||
|
putExtras(Bundle().apply {
|
||||||
|
putBinder(WebViewActivity.BINDER_KEY, scope.binder)
|
||||||
|
val pm = context.packageManager
|
||||||
|
val label = pm.getPackageInfo(pluginPackageName, 0).applicationInfo.loadLabel(pm).toString()
|
||||||
|
putString(WebViewActivity.TITLE_KEY, label)
|
||||||
|
})
|
||||||
|
setClassName(
|
||||||
|
hostPackageName,
|
||||||
|
WebViewActivity::class.qualifiedName!!
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
@ -2,20 +2,33 @@ package app.revanced.manager.plugin.downloader.webview
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.MenuItem
|
||||||
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 androidx.activity.ComponentActivity
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.activity.viewModels
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import app.revanced.manager.plugin.downloader.R
|
import app.revanced.manager.plugin.downloader.R
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
// TODO: use ComponentActivity instead.
|
class WebViewActivity : ComponentActivity() {
|
||||||
class WebViewActivity : AppCompatActivity() {
|
|
||||||
@SuppressLint("SetJavaScriptEnabled")
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
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 ->
|
||||||
@ -23,10 +36,16 @@ class WebViewActivity : AppCompatActivity() {
|
|||||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||||
insets
|
insets
|
||||||
}
|
}
|
||||||
val cookieManager = CookieManager.getInstance()
|
actionBar?.apply {
|
||||||
findViewById<WebView>(R.id.content).apply {
|
title = intent.getStringExtra(TITLE_KEY)
|
||||||
cookieManager.setAcceptCookie(true)
|
setHomeAsUpIndicator(android.R.drawable.ic_menu_close_clear_cancel)
|
||||||
// TODO: murder cookies if this is the first time setting it up.
|
setDisplayHomeAsUpEnabled(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
val events = IWebViewEvents.Stub.asInterface(intent.extras!!.getBinder(BINDER_KEY))!!
|
||||||
|
vm.setup(events)
|
||||||
|
|
||||||
|
val webView = findViewById<WebView>(R.id.content).apply {
|
||||||
settings.apply {
|
settings.apply {
|
||||||
cacheMode = WebSettings.LOAD_NO_CACHE
|
cacheMode = WebSettings.LOAD_NO_CACHE
|
||||||
databaseEnabled = false
|
databaseEnabled = false
|
||||||
@ -34,6 +53,86 @@ class WebViewActivity : AppCompatActivity() {
|
|||||||
domStorageEnabled = false
|
domStorageEnabled = false
|
||||||
javaScriptEnabled = true
|
javaScriptEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
webViewClient = vm.webViewClient
|
||||||
|
setDownloadListener { url, userAgent, _, mimetype, _ ->
|
||||||
|
vm.onDownload(url, mimetype, userAgent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
vm.commands.collect {
|
||||||
|
when (it) {
|
||||||
|
is WebViewModel.Command.Finish -> {
|
||||||
|
setResult(RESULT_OK)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
is WebViewModel.Command.Load -> webView.loadUrl(it.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem) = if (item.itemId == android.R.id.home) {
|
||||||
|
setResult(RESULT_CANCELED)
|
||||||
|
finish()
|
||||||
|
true
|
||||||
|
} else super.onOptionsItemSelected(item)
|
||||||
|
|
||||||
|
internal companion object {
|
||||||
|
const val BINDER_KEY = "EVENTS"
|
||||||
|
const val TITLE_KEY = "TITLE"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class WebViewModel : ViewModel() {
|
||||||
|
init {
|
||||||
|
CookieManager.getInstance().apply {
|
||||||
|
removeAllCookies(null)
|
||||||
|
setAcceptCookie(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val commandChannel = Channel<Command>()
|
||||||
|
val commands = commandChannel.receiveAsFlow()
|
||||||
|
|
||||||
|
private var eventBinder: IWebViewEvents? = null
|
||||||
|
private val ctrlBinder = object : IWebView.Stub() {
|
||||||
|
override fun load(url: String?) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
commandChannel.send(Command.Load(url!!))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun finish() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
commandChannel.send(Command.Finish)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val webViewClient = object : WebViewClient() {
|
||||||
|
override fun onPageFinished(view: WebView?, url: String?) {
|
||||||
|
super.onPageFinished(view, url)
|
||||||
|
eventBinder!!.pageLoad(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onDownload(url: String, mimeType: String, userAgent: String) {
|
||||||
|
eventBinder!!.download(url, mimeType, userAgent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setup(binder: IWebViewEvents) {
|
||||||
|
if (eventBinder != null) return
|
||||||
|
eventBinder = binder
|
||||||
|
binder.ready(ctrlBinder)
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface Command {
|
||||||
|
data class Load(val url: String) : Command
|
||||||
|
data object Finish : Command
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout 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:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/main"
|
android:id="@+id/main"
|
||||||
@ -11,7 +11,6 @@
|
|||||||
android:id="@+id/content"
|
android:id="@+id/content"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:rotationX="25"
|
|
||||||
tools:layout_editor_absoluteX="1dp"
|
tools:layout_editor_absoluteX="1dp"
|
||||||
tools:layout_editor_absoluteY="1dp" />
|
tools:layout_editor_absoluteY="1dp" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</LinearLayout>
|
7
downloader-plugin/src/main/res/values/themes.xml
Normal file
7
downloader-plugin/src/main/res/values/themes.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<style name="Theme.WebViewActivity" parent="Theme.AppCompat.DayNight">
|
||||||
|
<item name="android:windowActionBar">true</item>
|
||||||
|
<item name="android:windowNoTitle">false</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
@ -43,7 +43,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.compose.activity)
|
implementation(libs.activity.compose)
|
||||||
implementation(platform(libs.compose.bom))
|
implementation(platform(libs.compose.bom))
|
||||||
implementation(libs.compose.ui)
|
implementation(libs.compose.ui)
|
||||||
implementation(libs.compose.ui.tooling)
|
implementation(libs.compose.ui.tooling)
|
||||||
|
@ -3,16 +3,12 @@
|
|||||||
package app.revanced.manager.plugin.downloader.example
|
package app.revanced.manager.plugin.downloader.example
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.pm.PackageManager
|
import android.net.Uri
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import app.revanced.manager.plugin.downloader.download
|
|
||||||
import app.revanced.manager.plugin.downloader.downloader
|
import app.revanced.manager.plugin.downloader.downloader
|
||||||
import app.revanced.manager.plugin.downloader.requestStartActivity
|
import app.revanced.manager.plugin.downloader.webview.DownloadUrl
|
||||||
|
import app.revanced.manager.plugin.downloader.webview.webView
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import java.nio.file.Files
|
|
||||||
import kotlin.io.path.Path
|
|
||||||
import kotlin.io.path.fileSize
|
|
||||||
import kotlin.io.path.inputStream
|
|
||||||
|
|
||||||
// TODO: document API, update UI error presentation and strings
|
// TODO: document API, update UI error presentation and strings
|
||||||
|
|
||||||
@ -26,9 +22,10 @@ private val application by lazy {
|
|||||||
clazz.getMethod("getApplication")(activityThread) as Application
|
clazz.getMethod("getApplication")(activityThread) as Application
|
||||||
}
|
}
|
||||||
|
|
||||||
val installedAppDownloader = downloader<InstalledApp> {
|
val installedAppDownloader = downloader<DownloadUrl> {
|
||||||
val pm = application.packageManager
|
val pm = application.packageManager
|
||||||
|
|
||||||
|
/*
|
||||||
get { packageName, version ->
|
get { packageName, version ->
|
||||||
val packageInfo = try {
|
val packageInfo = try {
|
||||||
pm.getPackageInfo(packageName, 0)
|
pm.getPackageInfo(packageName, 0)
|
||||||
@ -40,8 +37,37 @@ val installedAppDownloader = downloader<InstalledApp> {
|
|||||||
requestStartActivity<InteractionActivity>()
|
requestStartActivity<InteractionActivity>()
|
||||||
|
|
||||||
InstalledApp(packageInfo.applicationInfo.sourceDir) to packageInfo.versionName
|
InstalledApp(packageInfo.applicationInfo.sourceDir) to packageInfo.versionName
|
||||||
|
}*/
|
||||||
|
webView { packageName, version ->
|
||||||
|
val startUrl = with(Uri.Builder()) {
|
||||||
|
scheme("https")
|
||||||
|
authority("www.apkmirror.com")
|
||||||
|
mapOf(
|
||||||
|
"post_type" to "app_release",
|
||||||
|
"searchtype" to "apk",
|
||||||
|
"s" to (version?.let { "$packageName $it" } ?: packageName),
|
||||||
|
"bundles%5B%5D" to "apk_files" // bundles[]
|
||||||
|
).forEach { (key, value) ->
|
||||||
|
appendQueryParameter(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
build().toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
onDownload { url, mimeType, userAgent ->
|
||||||
|
finish(DownloadUrl(url, mimeType, userAgent) to version)
|
||||||
|
}
|
||||||
|
|
||||||
|
onReady {
|
||||||
|
load(startUrl)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
download { downloadable ->
|
||||||
|
downloadable.toResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
download { app ->
|
download { app ->
|
||||||
with(Path(app.path)) { inputStream() to fileSize() }
|
with(Path(app.path)) { inputStream() to fileSize() }
|
||||||
}
|
}
|
||||||
@ -50,5 +76,5 @@ val installedAppDownloader = downloader<InstalledApp> {
|
|||||||
val path = Path(app.path)
|
val path = Path(app.path)
|
||||||
reportSize(path.fileSize())
|
reportSize(path.fileSize())
|
||||||
Files.copy(path, outputStream)
|
Files.copy(path, outputStream)
|
||||||
}
|
}*/
|
||||||
}
|
}
|
@ -4,7 +4,8 @@ material3 = "1.3.1"
|
|||||||
ui-tooling = "1.7.5"
|
ui-tooling = "1.7.5"
|
||||||
viewmodel-lifecycle = "2.8.7"
|
viewmodel-lifecycle = "2.8.7"
|
||||||
splash-screen = "1.0.1"
|
splash-screen = "1.0.1"
|
||||||
compose-activity = "1.9.3"
|
activity = "1.9.3"
|
||||||
|
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.11.00"
|
||||||
@ -37,21 +38,17 @@ compose-icons = "1.2.4"
|
|||||||
kotlin-process = "1.4.1"
|
kotlin-process = "1.4.1"
|
||||||
hidden-api-stub = "4.3.3"
|
hidden-api-stub = "4.3.3"
|
||||||
|
|
||||||
# TODO: get rid of these.
|
|
||||||
appcompat = "1.7.0"
|
|
||||||
material = "1.12.0"
|
|
||||||
activity = "1.9.1"
|
|
||||||
constraintlayout = "2.1.4"
|
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
# AndroidX Core
|
# AndroidX Core
|
||||||
androidx-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "ktx" }
|
androidx-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "ktx" }
|
||||||
runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "viewmodel-lifecycle" }
|
runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "viewmodel-lifecycle" }
|
||||||
runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "viewmodel-lifecycle" }
|
runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "viewmodel-lifecycle" }
|
||||||
splash-screen = { group = "androidx.core", name = "core-splashscreen", version.ref = "splash-screen" }
|
splash-screen = { group = "androidx.core", name = "core-splashscreen", version.ref = "splash-screen" }
|
||||||
compose-activity = { group = "androidx.activity", name = "activity-compose", version.ref = "compose-activity" }
|
activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity" }
|
||||||
|
activity-ktx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activity" }
|
||||||
work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "work-runtime" }
|
work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "work-runtime" }
|
||||||
preferences-datastore = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "preferences-datastore" }
|
preferences-datastore = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "preferences-datastore" }
|
||||||
|
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||||
|
|
||||||
# Compose
|
# Compose
|
||||||
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
|
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
|
||||||
@ -138,12 +135,6 @@ reorderable = { module = "sh.calvin.reorderable:reorderable", version.ref = "reo
|
|||||||
# switch to br.com.devsrsouza.compose.icons after DevSrSouza/compose-icons#30 is merged
|
# switch to br.com.devsrsouza.compose.icons after DevSrSouza/compose-icons#30 is merged
|
||||||
compose-icons-fontawesome = { group = "com.github.BenjaminHalko.compose-icons", name = "font-awesome", version.ref = "compose-icons" }
|
compose-icons-fontawesome = { group = "com.github.BenjaminHalko.compose-icons", name = "font-awesome", version.ref = "compose-icons" }
|
||||||
|
|
||||||
|
|
||||||
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
|
||||||
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
|
||||||
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
|
|
||||||
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
|
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" }
|
android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" }
|
||||||
android-library = { id = "com.android.library", version.ref = "android-gradle-plugin" }
|
android-library = { id = "com.android.library", version.ref = "android-gradle-plugin" }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user