feat: Final touch before merge

* Trust Extension rework
* Deep link
This commit is contained in:
ziro 2024-01-13 19:27:22 +07:00
parent 67f32d400d
commit addb84ee1e
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
12 changed files with 100 additions and 48 deletions

View file

@ -60,6 +60,16 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- Deep link to add repos -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="tachiyomi" />
<data android:host="add-repo" />
</intent-filter>
<meta-data android:name="android.app.searchable" android:resource="@xml/searchable"/>
</activity>
<activity
@ -270,4 +280,4 @@
</application>
</manifest>
</manifest>

View file

@ -0,0 +1,29 @@
package dev.yokai.domain.extension
import android.content.pm.PackageInfo
import androidx.core.content.pm.PackageInfoCompat
import dev.yokai.domain.source.SourcePreferences
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class TrustExtension(
private val sourcePreferences: SourcePreferences = Injekt.get(),
) {
fun isTrusted(pkgInfo: PackageInfo, signatureHash: String): Boolean {
val key = "${pkgInfo.packageName}:${PackageInfoCompat.getLongVersionCode(pkgInfo)}:$signatureHash"
return key in sourcePreferences.trustedExtensions().get()
}
fun trust(pkgName: String, versionCode: Long, signatureHash: String) {
sourcePreferences.trustedExtensions().let { exts ->
val removed = exts.get().filterNot { it.startsWith("$pkgName:") }.toMutableSet()
removed += "$pkgName:$versionCode:$signatureHash"
exts.set(removed)
}
}
fun revokeAll() {
sourcePreferences.trustedExtensions().delete()
}
}

View file

@ -1,18 +1,23 @@
package dev.yokai.presentation.extension.repo
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import eu.kanade.tachiyomi.ui.base.controller.BaseComposeController
class ExtensionRepoController :
class ExtensionRepoController() :
BaseComposeController() {
@Preview
private var repoUrl: String? = null
constructor(repoUrl: String) : this() {
this.repoUrl = repoUrl
}
@Composable
override fun ScreenContent() {
ExtensionRepoScreen(
title = "Extension Repos",
onBackPress = router::handleBack,
repoUrl = repoUrl,
)
}
}

View file

@ -4,11 +4,7 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.ExtensionOff
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
@ -31,6 +27,7 @@ fun ExtensionRepoScreen(
title: String,
onBackPress: () -> Unit,
viewModel: ExtensionRepoViewModel = viewModel(),
repoUrl: String? = null,
) {
val context = LocalContext.current
val repoState = viewModel.repoState.collectAsState()
@ -39,14 +36,6 @@ fun ExtensionRepoScreen(
YokaiScaffold(
onNavigationIconClicked = onBackPress,
title = title,
fab = {
FloatingActionButton(
containerColor = MaterialTheme.colorScheme.secondary,
onClick = { context.toast("Test") },
) {
Icon(Icons.Filled.Add, "Add repo")
}
},
appBarType = AppBarType.SMALL,
) { innerPadding ->
if (repoState.value is ExtensionRepoState.Loading) return@YokaiScaffold
@ -61,6 +50,7 @@ fun ExtensionRepoScreen(
item {
ExtensionRepoItem(
inputText = inputText,
// TODO: i18n
inputHint = "Add new repo",
onInputChange = { inputText = it },
onAddClick = { viewModel.addRepo(it) },
@ -72,6 +62,7 @@ fun ExtensionRepoScreen(
EmptyScreen(
modifier = Modifier.fillParentMaxSize(),
image = Icons.Filled.ExtensionOff,
// TODO: i18n
message = "No extension repo found",
)
}
@ -82,12 +73,17 @@ fun ExtensionRepoScreen(
item {
ExtensionRepoItem(
repoUrl = repo,
// TODO: Confirmation dialog
onDeleteClick = { viewModel.deleteRepo(it) },
)
}
}
}
}
LaunchedEffect(repoUrl) {
repoUrl?.let { viewModel.addRepo(repoUrl) }
}
LaunchedEffect(Unit) {
viewModel.event.collectLatest { event ->

View file

@ -255,6 +255,11 @@ object Migrations {
} catch (_: Exception) {
}
}
if (oldVersion < 112) {
prefs.edit {
remove("trusted_signatures")
}
}
return true
}

View file

@ -343,9 +343,6 @@ class PreferencesHelper(val context: Context, val preferenceStore: PreferenceSto
fun migrateFlags() = preferenceStore.getInt("migrate_flags", Int.MAX_VALUE)
// TODO: SourcePref
fun trustedSignatures() = preferenceStore.getStringSet("trusted_signatures", emptySet())
// using string instead of set so it is ordered
// TODO: SourcePref
fun migrationSources() = preferenceStore.getString("migrate_sources", "")

View file

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.di
import android.app.Application
import androidx.core.content.ContextCompat
import dev.yokai.domain.extension.TrustExtension
import eu.kanade.tachiyomi.core.preference.AndroidPreferenceStore
import eu.kanade.tachiyomi.core.preference.PreferenceStore
import eu.kanade.tachiyomi.data.cache.ChapterCache
@ -61,6 +62,8 @@ class AppModule(val app: Application) : InjektModule {
addSingletonFactory { MangaShortcutManager() }
addSingletonFactory { TrustExtension() }
// Asynchronously init expensive components for a faster cold start
ContextCompat.getMainExecutor(app).execute {

View file

@ -4,9 +4,8 @@ import android.content.Context
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Parcelable
import dev.yokai.domain.source.SourcePreferences
import dev.yokai.domain.extension.TrustExtension
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.minusAssign
import eu.kanade.tachiyomi.data.preference.plusAssign
import eu.kanade.tachiyomi.extension.api.ExtensionApi
import eu.kanade.tachiyomi.extension.model.Extension
@ -23,7 +22,6 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.parcelize.Parcelize
import timber.log.Timber
import uy.kohesive.injekt.Injekt
@ -43,6 +41,7 @@ import java.util.Locale
class ExtensionManager(
private val context: Context,
private val preferences: PreferencesHelper = Injekt.get(),
private val trustExtension: TrustExtension = Injekt.get(),
) {
/**
@ -307,34 +306,30 @@ class ExtensionManager(
}
/**
* Adds the given signature to the list of trusted signatures. It also loads in background the
* extensions that match this signature.
* Adds the given extension to the list of trusted extensions. It also loads in background the
* now trusted extensions.
*
* @param signature The signature to whitelist.
* @param pkgName the package name of the extension
* @param versionCode the version code of the extension
* @param signatureHash the signature hash of the extension
*/
fun trustSignature(signature: String) {
val untrustedSignatures = untrustedExtensionsFlow.value.map { it.signatureHash }.toSet()
if (signature !in untrustedSignatures) return
fun trust(pkgName: String, versionCode: Long, signatureHash: String) {
val untrustedPkgName = untrustedExtensionsFlow.value.map { it.pkgName }.toSet()
if (pkgName !in untrustedPkgName) return
ExtensionLoader.trustedSignatures += signature
val preference = preferences.trustedSignatures()
preference.set(preference.get() + signature)
trustExtension.trust(pkgName, versionCode, signatureHash)
val nowTrustedExtensions = untrustedExtensionsFlow.value.filter { it.signatureHash == signature }
val nowTrustedExtensions = untrustedExtensionsFlow.value
.filter { it.pkgName == pkgName && it.versionCode == versionCode }
_untrustedExtensionsFlow.value -= nowTrustedExtensions
val ctx = context
launchNow {
nowTrustedExtensions
.map { extension ->
async { ExtensionLoader.loadExtensionFromPkgName(ctx, extension.pkgName) }
}
.map { it.await() }
.forEach { result ->
if (result is LoadResult.Success) {
registerNewExtension(result.extension)
}
async { ExtensionLoader.loadExtensionFromPkgName(context, extension.pkgName) }.await()
}
.filterIsInstance<LoadResult.Success>()
.forEach { registerNewExtension(it.extension) }
}
}

View file

@ -274,7 +274,7 @@ class ExtensionBottomPresenter : BaseMigrationPresenter<ExtensionBottomSheet>()
}
}
fun trustSignature(signatureHash: String) {
extensionManager.trustSignature(signatureHash)
fun trustExtension(pkgName: String, versionCode: Long, signatureHash: String) {
extensionManager.trust(pkgName, versionCode, signatureHash)
}
}

View file

@ -323,7 +323,7 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At
}
private fun openTrustDialog(extension: Extension.Untrusted) {
ExtensionTrustDialog(this, extension.signatureHash, extension.pkgName)
ExtensionTrustDialog(this, extension.signatureHash, extension.pkgName, extension.versionCode)
.showDialog(controller.router)
}
@ -407,9 +407,10 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At
extAdapter?.updateItem(updateHeader)
}
override fun trustSignature(signatureHash: String) {
presenter.trustSignature(signatureHash)
override fun trustExtension(pkgName: String, versionCode: Long, signatureHash: String) {
presenter.trustExtension(pkgName, versionCode, signatureHash)
}
override fun uninstallExtension(pkgName: String) {
presenter.uninstallExtension(pkgName)
}

View file

@ -10,10 +10,11 @@ class ExtensionTrustDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
where T : ExtensionTrustDialog.Listener {
lateinit var listener: Listener
constructor(target: T, signatureHash: String, pkgName: String) : this(
constructor(target: T, signatureHash: String, pkgName: String, versionCode: Long) : this(
Bundle().apply {
putString(SIGNATURE_KEY, signatureHash)
putString(PKGNAME_KEY, pkgName)
putLong(VERSION_CODE, versionCode)
},
) {
listener = target
@ -24,7 +25,7 @@ class ExtensionTrustDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
.setTitle(R.string.untrusted_extension)
.setMessage(R.string.untrusted_extension_message)
.setPositiveButton(R.string.trust) { _, _ ->
listener.trustSignature(args.getString(SIGNATURE_KEY)!!)
listener.trustExtension(args.getString(PKGNAME_KEY)!!, args.getLong(VERSION_CODE), args.getString(SIGNATURE_KEY)!!)
}
.setNegativeButton(R.string.uninstall) { _, _ ->
listener.uninstallExtension(args.getString(PKGNAME_KEY)!!)
@ -34,10 +35,11 @@ class ExtensionTrustDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
private companion object {
const val SIGNATURE_KEY = "signature_key"
const val PKGNAME_KEY = "pkgname_key"
const val VERSION_CODE = "version_code"
}
interface Listener {
fun trustSignature(signatureHash: String)
fun trustExtension(pkgName: String, versionCode: Long, signatureHash: String)
fun uninstallExtension(pkgName: String)
}
}

View file

@ -70,6 +70,7 @@ import com.google.android.material.snackbar.Snackbar
import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback
import com.google.common.primitives.Floats.max
import com.google.common.primitives.Ints.max
import dev.yokai.presentation.extension.repo.ExtensionRepoController
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.Migrations
import eu.kanade.tachiyomi.R
@ -1053,6 +1054,14 @@ open class MainActivity : BaseActivity<MainActivityBinding>() {
controller?.showSheet()
}
}
Intent.ACTION_VIEW -> {
if (intent.scheme == "tachiyomi" && intent.data?.host == "add-repo") {
intent.data?.getQueryParameter("url")?.let { repoUrl ->
router.popToRoot()
router.pushController(ExtensionRepoController(repoUrl).withFadeTransaction())
}
}
}
else -> return false
}
return true