mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-11-27 12:05:30 +00:00
Module json add changelog
This commit is contained in:
parent
691e41e22e
commit
bf8b74e996
@ -78,18 +78,13 @@ dependencies {
|
||||
implementation("dev.rikka.rikkax.layoutinflater:layoutinflater:1.2.0")
|
||||
implementation("dev.rikka.rikkax.insets:insets:1.1.1")
|
||||
implementation("dev.rikka.rikkax.recyclerview:recyclerview-ktx:1.3.1")
|
||||
implementation("io.noties.markwon:core:4.6.2")
|
||||
|
||||
val vBAdapt = "4.0.0"
|
||||
val bindingAdapter = "me.tatarka.bindingcollectionadapter2:bindingcollectionadapter"
|
||||
implementation("${bindingAdapter}:${vBAdapt}")
|
||||
implementation("${bindingAdapter}-recyclerview:${vBAdapt}")
|
||||
|
||||
val vMarkwon = "4.6.2"
|
||||
implementation("io.noties.markwon:core:${vMarkwon}")
|
||||
implementation("io.noties.markwon:html:${vMarkwon}")
|
||||
implementation("io.noties.markwon:image:${vMarkwon}")
|
||||
implementation("com.caverock:androidsvg:1.4")
|
||||
|
||||
val vLibsu = "3.2.1"
|
||||
implementation("com.github.topjohnwu.libsu:core:${vLibsu}")
|
||||
implementation("com.github.topjohnwu.libsu:io:${vLibsu}")
|
||||
|
@ -14,20 +14,19 @@ import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.ForegroundTracker
|
||||
import com.topjohnwu.magisk.core.base.BaseService
|
||||
import com.topjohnwu.magisk.core.intent
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||
import com.topjohnwu.magisk.core.utils.ProgressInputStream
|
||||
import com.topjohnwu.magisk.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.ktx.copyAndClose
|
||||
import com.topjohnwu.magisk.ktx.synchronized
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import com.topjohnwu.magisk.view.Notifications.mgr
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.ResponseBody
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
class DownloadService : BaseService() {
|
||||
|
||||
@ -67,11 +66,11 @@ class DownloadService : BaseService() {
|
||||
val stream = service.fetchFile(subject.url).toProgressStream(subject)
|
||||
when (subject) {
|
||||
is Subject.Manager -> handleAPK(subject, stream)
|
||||
else -> stream.copyAndClose(subject.file.outputStream())
|
||||
else -> stream.toModule(subject.file, service.fetchInstaller().byteStream())
|
||||
}
|
||||
if (ForegroundTracker.hasForeground) {
|
||||
remove(subject.notifyId)
|
||||
subject.pendingIntent(this@DownloadService).send()
|
||||
subject.pendingIntent(this@DownloadService)?.send()
|
||||
} else {
|
||||
notifyFinish(subject)
|
||||
}
|
||||
|
@ -1,14 +1,18 @@
|
||||
package com.topjohnwu.magisk.core.download
|
||||
|
||||
import android.app.AlarmManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.net.toFile
|
||||
import com.topjohnwu.magisk.DynAPK
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.ForegroundTracker
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||
import com.topjohnwu.magisk.core.tasks.HideAPK
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||
import com.topjohnwu.magisk.ktx.copyAndClose
|
||||
import com.topjohnwu.magisk.ktx.relaunchApp
|
||||
import com.topjohnwu.magisk.ktx.writeTo
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
@ -55,9 +59,17 @@ suspend fun DownloadService.handleAPK(subject: Subject.Manager, stream: InputStr
|
||||
apk.delete()
|
||||
patched.renameTo(apk)
|
||||
} else {
|
||||
// Simply relaunch the app
|
||||
val intent = packageManager.getLaunchIntentForPackage(packageName)
|
||||
intent!!.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
//noinspection InlinedApi
|
||||
val flag = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
val pending = PendingIntent.getActivity(this, id, intent, flag)
|
||||
if (ForegroundTracker.hasForeground) {
|
||||
val alarm = getSystemService<AlarmManager>()
|
||||
alarm!!.set(AlarmManager.RTC, System.currentTimeMillis() + 1000, pending)
|
||||
}
|
||||
stopSelf()
|
||||
relaunchApp(this)
|
||||
Runtime.getRuntime().exit(0)
|
||||
}
|
||||
} else {
|
||||
write(subject.file.outputStream())
|
||||
|
@ -0,0 +1,38 @@
|
||||
package com.topjohnwu.magisk.core.download
|
||||
|
||||
import android.net.Uri
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||
import com.topjohnwu.magisk.ktx.forEach
|
||||
import com.topjohnwu.magisk.ktx.withStreams
|
||||
import java.io.InputStream
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
fun InputStream.toModule(file: Uri, installer: InputStream) {
|
||||
|
||||
val input = ZipInputStream(buffered())
|
||||
val output = ZipOutputStream(file.outputStream().buffered())
|
||||
|
||||
withStreams(input, output) { zin, zout ->
|
||||
zout.putNextEntry(ZipEntry("META-INF/"))
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/"))
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/google/"))
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/google/android/"))
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/google/android/update-binary"))
|
||||
installer.copyTo(zout)
|
||||
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/google/android/updater-script"))
|
||||
zout.write("#MAGISK\n".toByteArray(charset("UTF-8")))
|
||||
|
||||
zin.forEach { entry ->
|
||||
val path = entry.name
|
||||
if (path.isNotEmpty() && !path.startsWith("META-INF")) {
|
||||
zout.putNextEntry(ZipEntry(path))
|
||||
if (!entry.isDirectory) {
|
||||
zin.copyTo(zout)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -35,7 +35,7 @@ sealed class Subject : Parcelable {
|
||||
abstract val title: String
|
||||
abstract val notifyId: Int
|
||||
|
||||
abstract fun pendingIntent(context: Context): PendingIntent
|
||||
abstract fun pendingIntent(context: Context): PendingIntent?
|
||||
|
||||
@Parcelize
|
||||
class Module(
|
||||
@ -53,7 +53,7 @@ sealed class Subject : Parcelable {
|
||||
|
||||
override fun pendingIntent(context: Context) = when (action) {
|
||||
Action.Flash -> FlashFragment.installIntent(context, file)
|
||||
else -> Intent().toPending(context)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,7 @@ data class ModuleJson(
|
||||
val version: String,
|
||||
val versionCode: Int,
|
||||
val zipUrl: String,
|
||||
val changelog: String,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
|
@ -11,9 +11,10 @@ data class OnlineModule(
|
||||
override var version: String,
|
||||
override var versionCode: Int,
|
||||
val zipUrl: String,
|
||||
val changelog: String,
|
||||
) : Module(), Parcelable {
|
||||
constructor(local: LocalModule, json: ModuleJson) :
|
||||
this(local.id, local.name, json.version, json.versionCode, json.zipUrl)
|
||||
this(local.id, local.name, json.version, json.versionCode, json.zipUrl, json.changelog)
|
||||
|
||||
val downloadFilename get() = "$name-$version($versionCode).zip".legalFilename()
|
||||
|
||||
|
@ -4,6 +4,7 @@ import android.animation.ValueAnimator
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Paint
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.Spanned
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@ -29,11 +30,8 @@ import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.ktx.coroutineScope
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||
import com.topjohnwu.widget.IndeterminateCheckBox
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@BindingAdapter("gone")
|
||||
@ -57,10 +55,8 @@ fun setInvisibleUnless(view: View, invisibleUnless: Boolean) {
|
||||
}
|
||||
|
||||
@BindingAdapter("markdownText")
|
||||
fun setMarkdownText(tv: TextView, text: CharSequence) {
|
||||
tv.coroutineScope.launch(Dispatchers.IO) {
|
||||
ServiceLocator.markwon.setMarkdown(tv, text.toString())
|
||||
}
|
||||
fun setMarkdownText(tv: TextView, markdown: Spanned) {
|
||||
ServiceLocator.markwon.setParsedMarkdown(tv, markdown)
|
||||
}
|
||||
|
||||
@BindingAdapter("onNavigationClick")
|
||||
|
@ -1,16 +1,17 @@
|
||||
package com.topjohnwu.magisk.di
|
||||
|
||||
import android.content.Context
|
||||
import android.text.method.LinkMovementMethod
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.topjohnwu.magisk.BuildConfig
|
||||
import com.topjohnwu.magisk.ProviderInstaller
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.ktx.precomputedText
|
||||
import com.topjohnwu.magisk.utils.MarkwonImagePlugin
|
||||
import com.topjohnwu.magisk.core.utils.currentLocale
|
||||
import io.noties.markwon.Markwon
|
||||
import io.noties.markwon.html.HtmlPlugin
|
||||
import io.noties.markwon.utils.NoCopySpannableFactory
|
||||
import okhttp3.Cache
|
||||
import okhttp3.ConnectionSpec
|
||||
import okhttp3.Dns
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
@ -46,7 +47,8 @@ private class DnsResolver(client: OkHttpClient) : Dns {
|
||||
if (Config.doh) {
|
||||
try {
|
||||
return doh.lookup(hostname)
|
||||
} catch (e: UnknownHostException) {}
|
||||
} catch (e: UnknownHostException) {
|
||||
}
|
||||
}
|
||||
return Dns.SYSTEM.lookup(hostname)
|
||||
}
|
||||
@ -61,11 +63,16 @@ fun createOkHttpClient(context: Context): OkHttpClient {
|
||||
builder.addInterceptor(HttpLoggingInterceptor().apply {
|
||||
level = HttpLoggingInterceptor.Level.BASIC
|
||||
})
|
||||
} else {
|
||||
builder.connectionSpecs(listOf(ConnectionSpec.RESTRICTED_TLS))
|
||||
}
|
||||
|
||||
builder.dns(DnsResolver(builder.build()))
|
||||
|
||||
builder.addInterceptor { chain ->
|
||||
val request = chain.request().newBuilder()
|
||||
request.header("User-Agent", "Magisk ${BuildConfig.VERSION_CODE}")
|
||||
request.header("User-Agent", "Magisk/${BuildConfig.VERSION_CODE}")
|
||||
request.header("Accept-Language", currentLocale.toLanguageTag())
|
||||
chain.proceed(request.build())
|
||||
}
|
||||
|
||||
@ -73,7 +80,6 @@ fun createOkHttpClient(context: Context): OkHttpClient {
|
||||
Info.hasGMS = false
|
||||
}
|
||||
|
||||
builder.dns(DnsResolver(builder.build()))
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
@ -96,13 +102,17 @@ inline fun <reified T> createApiService(retrofitBuilder: Retrofit.Builder, baseU
|
||||
.create(T::class.java)
|
||||
}
|
||||
|
||||
fun createMarkwon(context: Context, okHttpClient: OkHttpClient): Markwon {
|
||||
fun createMarkwon(context: Context): Markwon {
|
||||
return Markwon.builder(context)
|
||||
.textSetter { textView, spanned, _, onComplete ->
|
||||
textView.tag = onComplete
|
||||
textView.precomputedText = spanned
|
||||
.textSetter { textView, spanned, bufferType, onComplete ->
|
||||
textView.apply {
|
||||
post {
|
||||
movementMethod = LinkMovementMethod.getInstance()
|
||||
setSpannableFactory(NoCopySpannableFactory.getInstance())
|
||||
setText(spanned, bufferType)
|
||||
onComplete.run()
|
||||
}
|
||||
}
|
||||
}
|
||||
.usePlugin(HtmlPlugin.create())
|
||||
.usePlugin(MarkwonImagePlugin(okHttpClient))
|
||||
.build()
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ object ServiceLocator {
|
||||
// Networking
|
||||
val okhttp by lazy { createOkHttpClient(context) }
|
||||
val retrofit by lazy { createRetrofit(okhttp) }
|
||||
val markwon by lazy { createMarkwon(context, okhttp) }
|
||||
val markwon by lazy { createMarkwon(context) }
|
||||
val networkService by lazy {
|
||||
NetworkService(
|
||||
createApiService(retrofit, Const.Url.GITHUB_PAGE_URL),
|
||||
|
@ -10,9 +10,8 @@ import com.topjohnwu.magisk.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.view.MagiskDialog
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
import java.io.IOException
|
||||
|
||||
abstract class MarkDownDialog : DialogEvent() {
|
||||
|
||||
@ -23,17 +22,13 @@ abstract class MarkDownDialog : DialogEvent() {
|
||||
with(dialog) {
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.markdown_window_md2, null)
|
||||
setView(view)
|
||||
(ownerActivity as BaseActivity).lifecycleScope.launch {
|
||||
val tv = view.findViewById<TextView>(R.id.md_txt)
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
ServiceLocator.markwon.setMarkdown(tv, getMarkdownText())
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException)
|
||||
throw e
|
||||
Timber.e(e)
|
||||
tv.post { tv.setText(R.string.download_file_error) }
|
||||
}
|
||||
val tv = view.findViewById<TextView>(R.id.md_txt)
|
||||
(ownerActivity as BaseActivity).lifecycleScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
ServiceLocator.markwon.setMarkdown(tv, getMarkdownText())
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
tv.post { tv.setText(R.string.download_file_error) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,24 @@
|
||||
package com.topjohnwu.magisk.events.dialog
|
||||
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.download.Action
|
||||
import com.topjohnwu.magisk.core.download.DownloadService
|
||||
import com.topjohnwu.magisk.core.download.Subject
|
||||
import com.topjohnwu.magisk.core.model.module.OnlineModule
|
||||
import com.topjohnwu.magisk.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.view.MagiskDialog
|
||||
|
||||
class ModuleInstallDialog(private val item: OnlineModule) : DialogEvent() {
|
||||
class ModuleInstallDialog(private val item: OnlineModule) : MarkDownDialog() {
|
||||
|
||||
private val svc get() = ServiceLocator.networkService
|
||||
|
||||
override suspend fun getMarkdownText(): String {
|
||||
val str = svc.fetchString(item.changelog)
|
||||
return if (str.length > 1000) str.substring(0, 1000) else str
|
||||
}
|
||||
|
||||
override fun build(dialog: MagiskDialog) {
|
||||
super.build(dialog)
|
||||
dialog.apply {
|
||||
|
||||
fun download(install: Boolean) {
|
||||
@ -19,21 +27,21 @@ class ModuleInstallDialog(private val item: OnlineModule) : DialogEvent() {
|
||||
DownloadService.start(context, subject)
|
||||
}
|
||||
|
||||
setTitle(context.getString(R.string.repo_install_title, item.name))
|
||||
setMessage(context.getString(R.string.repo_install_msg, item.downloadFilename))
|
||||
val title = context.getString(R.string.repo_install_title,
|
||||
item.name, item.version, item.versionCode)
|
||||
|
||||
setTitle(title)
|
||||
setCancelable(true)
|
||||
setButton(MagiskDialog.ButtonType.NEGATIVE) {
|
||||
text = R.string.download
|
||||
icon = R.drawable.ic_download_md2
|
||||
onClick { download(false) }
|
||||
}
|
||||
|
||||
if (Info.env.isActive) {
|
||||
setButton(MagiskDialog.ButtonType.POSITIVE) {
|
||||
text = R.string.install
|
||||
icon = R.drawable.ic_install
|
||||
onClick { download(true) }
|
||||
}
|
||||
setButton(MagiskDialog.ButtonType.POSITIVE) {
|
||||
text = R.string.install
|
||||
onClick { download(true) }
|
||||
}
|
||||
setButton(MagiskDialog.ButtonType.NEUTRAL) {
|
||||
text = android.R.string.cancel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -299,53 +299,6 @@ val View.activity: Activity get() {
|
||||
}
|
||||
}
|
||||
|
||||
var View.coroutineScope: CoroutineScope
|
||||
get() = getTag(R.id.coroutineScope) as? CoroutineScope
|
||||
?: (activity as? BaseActivity)?.lifecycleScope
|
||||
?: GlobalScope
|
||||
set(value) = setTag(R.id.coroutineScope, value)
|
||||
|
||||
@set:BindingAdapter("precomputedText")
|
||||
var TextView.precomputedText: CharSequence
|
||||
get() = text
|
||||
set(value) {
|
||||
val callback = tag as? Runnable
|
||||
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
if (SDK_INT >= 29) {
|
||||
// Internally PrecomputedTextCompat will use platform API on API 29+
|
||||
// Due to some stupid crap OEM (Samsung) implementation, this can actually
|
||||
// crash our app. Directly use platform APIs with some workarounds
|
||||
val pre = PrecomputedText.create(value, textMetricsParams)
|
||||
post {
|
||||
try {
|
||||
text = pre
|
||||
} catch (e: IllegalArgumentException) {
|
||||
// Override to computed params to workaround crashes
|
||||
textMetricsParams = pre.params
|
||||
text = pre
|
||||
}
|
||||
isGone = false
|
||||
callback?.run()
|
||||
}
|
||||
} else {
|
||||
val tv = this@precomputedText
|
||||
val params = TextViewCompat.getTextMetricsParams(tv)
|
||||
val pre = PrecomputedTextCompat.create(value, params)
|
||||
post {
|
||||
TextViewCompat.setPrecomputedText(tv, pre)
|
||||
isGone = false
|
||||
callback?.run()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Int.dpInPx(): Int {
|
||||
val scale = AppContext.resources.displayMetrics.density
|
||||
return (this * scale + 0.5).toInt()
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateApi")
|
||||
fun getProperty(key: String, def: String): String {
|
||||
runCatching {
|
||||
|
@ -4,12 +4,10 @@ import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.BaseUIFragment
|
||||
import com.topjohnwu.magisk.databinding.FragmentInstallMd2Binding
|
||||
import com.topjohnwu.magisk.di.viewModel
|
||||
import com.topjohnwu.magisk.ktx.coroutineScope
|
||||
|
||||
class InstallFragment : BaseUIFragment<InstallViewModel, FragmentInstallMd2Binding>() {
|
||||
|
||||
@ -19,9 +17,6 @@ class InstallFragment : BaseUIFragment<InstallViewModel, FragmentInstallMd2Bindi
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
requireActivity().setTitle(R.string.install)
|
||||
|
||||
// Allow markwon to run in viewmodel scope
|
||||
binding.releaseNotes.coroutineScope = viewModel.viewModelScope
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
|
@ -1,6 +1,8 @@
|
||||
package com.topjohnwu.magisk.ui.install
|
||||
|
||||
import android.net.Uri
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.Spanned
|
||||
import androidx.databinding.Bindable
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.topjohnwu.magisk.BR
|
||||
@ -12,10 +14,12 @@ import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.data.repository.NetworkService
|
||||
import com.topjohnwu.magisk.databinding.set
|
||||
import com.topjohnwu.magisk.di.AppContext
|
||||
import com.topjohnwu.magisk.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.events.MagiskInstallFileEvent
|
||||
import com.topjohnwu.magisk.events.dialog.SecondSlotWarningDialog
|
||||
import com.topjohnwu.magisk.ui.flash.FlashFragment
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
@ -55,23 +59,23 @@ class InstallViewModel(
|
||||
set(value) = set(value, field, { field = it }, BR.data)
|
||||
|
||||
@get:Bindable
|
||||
var notes = ""
|
||||
var notes: Spanned = SpannableStringBuilder()
|
||||
set(value) = set(value, field, { field = it }, BR.notes)
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
File(AppContext.cacheDir, "${BuildConfig.VERSION_CODE}.md").run {
|
||||
notes = when {
|
||||
exists() -> readText()
|
||||
Const.Url.CHANGELOG_URL.isEmpty() -> ""
|
||||
else -> {
|
||||
val text = svc.fetchString(Const.Url.CHANGELOG_URL)
|
||||
writeText(text)
|
||||
text
|
||||
}
|
||||
val file = File(AppContext.cacheDir, "${BuildConfig.VERSION_CODE}.md")
|
||||
val text = when {
|
||||
file.exists() -> file.readText()
|
||||
Const.Url.CHANGELOG_URL.isEmpty() -> ""
|
||||
else -> {
|
||||
val str = svc.fetchString(Const.Url.CHANGELOG_URL)
|
||||
file.writeText(str)
|
||||
str
|
||||
}
|
||||
}
|
||||
notes = ServiceLocator.markwon.toMarkdown(text)
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
}
|
||||
|
@ -1,233 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.PictureDrawable
|
||||
import android.graphics.drawable.ShapeDrawable
|
||||
import android.net.Uri
|
||||
import android.text.Spanned
|
||||
import android.text.style.DynamicDrawableSpan
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.WorkerThread
|
||||
import com.caverock.androidsvg.SVG
|
||||
import com.caverock.androidsvg.SVGParseException
|
||||
import com.topjohnwu.magisk.di.AppContext
|
||||
import com.topjohnwu.superuser.internal.WaitRunnable
|
||||
import io.noties.markwon.AbstractMarkwonPlugin
|
||||
import io.noties.markwon.MarkwonSpansFactory
|
||||
import io.noties.markwon.image.*
|
||||
import io.noties.markwon.image.data.DataUriSchemeHandler
|
||||
import io.noties.markwon.image.network.OkHttpNetworkSchemeHandler
|
||||
import kotlinx.coroutines.*
|
||||
import okhttp3.OkHttpClient
|
||||
import org.commonmark.node.Image
|
||||
import timber.log.Timber
|
||||
import java.io.InputStream
|
||||
|
||||
// Differences with Markwon stock ImagePlugin:
|
||||
//
|
||||
// We assume beforeSetText() will be run in a background thread, and in that method
|
||||
// we download/decode all drawables before sending the spanned markdown CharSequence
|
||||
// to the next stage. We also get our surrounding TextView width to properly
|
||||
// resize our images.
|
||||
//
|
||||
// This is required for PrecomputedText to properly take the images into account
|
||||
// when precomputing the metrics of TextView
|
||||
//
|
||||
// Basically, we want nothing to do with AsyncDrawable
|
||||
class MarkwonImagePlugin(okHttp: OkHttpClient) : AbstractMarkwonPlugin() {
|
||||
|
||||
override fun configureSpansFactory(builder: MarkwonSpansFactory.Builder) {
|
||||
builder.setFactory(Image::class.java) { _, props ->
|
||||
val dest = ImageProps.DESTINATION.require(props)
|
||||
val size = ImageProps.IMAGE_SIZE.get(props)
|
||||
ImageSpan(dest, size)
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
override fun beforeSetText(tv: TextView, markdown: Spanned) {
|
||||
if (markdown.isEmpty())
|
||||
return
|
||||
|
||||
val spans = markdown.getSpans(0, markdown.length, ImageSpan::class.java)
|
||||
if (spans == null || spans.isEmpty())
|
||||
return
|
||||
|
||||
// Get TextView sizes before setText() to resize all images
|
||||
val wr = WaitRunnable {
|
||||
val width = tv.width - tv.paddingLeft - tv.paddingRight
|
||||
spans.forEach { it.canvasWidth = width }
|
||||
}
|
||||
tv.post(wr)
|
||||
|
||||
runBlocking {
|
||||
// Wait for drawable to be set
|
||||
spans.forEach { it.await() }
|
||||
// Wait for canvasWidth to be set
|
||||
wr.waitUntilDone()
|
||||
}
|
||||
}
|
||||
|
||||
private val schemeHandlers = HashMap<String, SchemeHandler>(3)
|
||||
private val mediaDecoders = HashMap<String, MediaDecoder>(0)
|
||||
private val defaultMediaDecoder = DefaultMediaDecoder.create()
|
||||
|
||||
init {
|
||||
addSchemeHandler(DataUriSchemeHandler.create())
|
||||
addSchemeHandler(OkHttpNetworkSchemeHandler.create(okHttp))
|
||||
addMediaDecoder(SVGDecoder())
|
||||
}
|
||||
|
||||
private fun addSchemeHandler(schemeHandler: SchemeHandler) {
|
||||
for (scheme in schemeHandler.supportedSchemes()) {
|
||||
schemeHandlers[scheme] = schemeHandler
|
||||
}
|
||||
}
|
||||
|
||||
private fun addMediaDecoder(mediaDecoder: MediaDecoder) {
|
||||
for (type in mediaDecoder.supportedTypes()) {
|
||||
mediaDecoders[type] = mediaDecoder
|
||||
}
|
||||
}
|
||||
|
||||
// Modified from AsyncDrawableLoaderImpl.execute(asyncDrawable)
|
||||
fun loadDrawable(destination: String): Drawable? {
|
||||
val uri = Uri.parse(destination)
|
||||
var drawable: Drawable? = null
|
||||
|
||||
try {
|
||||
val scheme = uri.scheme
|
||||
check(scheme != null && scheme.isNotEmpty()) {
|
||||
"No scheme is found: $destination"
|
||||
}
|
||||
|
||||
// obtain scheme handler
|
||||
val schemeHandler = schemeHandlers[scheme]
|
||||
?: throw IllegalStateException("No scheme-handler is found: $destination")
|
||||
|
||||
// handle scheme
|
||||
val imageItem = schemeHandler.handle(destination, uri)
|
||||
|
||||
// if resulting imageItem needs further decoding -> proceed
|
||||
drawable = if (imageItem.hasDecodingNeeded()) {
|
||||
val withDecodingNeeded = imageItem.asWithDecodingNeeded
|
||||
val mediaDecoder = mediaDecoders[withDecodingNeeded.contentType()]
|
||||
?: defaultMediaDecoder
|
||||
mediaDecoder.decode(
|
||||
withDecodingNeeded.contentType(),
|
||||
withDecodingNeeded.inputStream()
|
||||
)
|
||||
} else {
|
||||
imageItem.asWithResult.result()
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
Timber.e(t, "Error loading image: $destination")
|
||||
}
|
||||
|
||||
// apply intrinsic bounds (but only if they are empty)
|
||||
if (drawable != null && drawable.bounds.isEmpty)
|
||||
DrawableUtils.applyIntrinsicBounds(drawable)
|
||||
|
||||
return drawable
|
||||
}
|
||||
|
||||
inner class ImageSpan(
|
||||
dest: String,
|
||||
private val size: ImageSize?
|
||||
) : DynamicDrawableSpan(ALIGN_BOTTOM) {
|
||||
|
||||
var canvasWidth = 0
|
||||
private var measured = false
|
||||
private lateinit var draw: Drawable
|
||||
private val job: Job
|
||||
|
||||
init {
|
||||
// Asynchronously download/decode images in the background
|
||||
job = GlobalScope.launch(Dispatchers.IO) {
|
||||
draw = loadDrawable(dest) ?: ShapeDrawable()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun await() = job.join()
|
||||
|
||||
override fun getDrawable() = draw
|
||||
|
||||
private fun defaultBounds(): Rect {
|
||||
val bounds: Rect = draw.bounds
|
||||
if (!bounds.isEmpty) {
|
||||
return bounds
|
||||
}
|
||||
val intrinsicBounds = DrawableUtils.intrinsicBounds(draw)
|
||||
if (!intrinsicBounds.isEmpty) {
|
||||
return intrinsicBounds
|
||||
}
|
||||
return Rect(0, 0, 1, 1)
|
||||
}
|
||||
|
||||
private fun measure(paint: Paint) {
|
||||
if (measured || canvasWidth == 0)
|
||||
return
|
||||
measured = true
|
||||
val bound =
|
||||
SizeResolver.resolveImageSize(size, defaultBounds(), canvasWidth, paint.textSize)
|
||||
draw.bounds = bound
|
||||
}
|
||||
|
||||
override fun getSize(
|
||||
paint: Paint, text: CharSequence?, start: Int, end: Int, fm: Paint.FontMetricsInt?
|
||||
): Int {
|
||||
measure(paint)
|
||||
return super.getSize(paint, text, start, end, fm)
|
||||
}
|
||||
}
|
||||
|
||||
object SizeResolver : ImageSizeResolverDef() {
|
||||
// Expose protected API
|
||||
public override fun resolveImageSize(
|
||||
imageSize: ImageSize?,
|
||||
imageBounds: Rect,
|
||||
canvasWidth: Int,
|
||||
textSize: Float
|
||||
): Rect {
|
||||
return super.resolveImageSize(imageSize, imageBounds, canvasWidth, textSize)
|
||||
}
|
||||
}
|
||||
|
||||
class SVGDecoder: MediaDecoder() {
|
||||
override fun supportedTypes() = listOf("image/svg+xml")
|
||||
|
||||
override fun decode(contentType: String?, inputStream: InputStream): Drawable {
|
||||
val svg = try {
|
||||
SVG.getFromInputStream(inputStream)
|
||||
} catch (e: SVGParseException) {
|
||||
throw IllegalStateException("Exception decoding SVG", e)
|
||||
}
|
||||
|
||||
val w = svg.documentWidth
|
||||
val h = svg.documentHeight
|
||||
|
||||
if (w <= 0 || h <= 0) {
|
||||
val picture = svg.renderToPicture()
|
||||
return PictureDrawable(picture)
|
||||
}
|
||||
|
||||
val density: Float = AppContext.resources.displayMetrics.density
|
||||
|
||||
val width = (w * density + .5f).toInt()
|
||||
val height = (h * density + .5f).toInt()
|
||||
|
||||
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(bitmap)
|
||||
canvas.scale(density, density)
|
||||
svg.renderToCanvas(canvas)
|
||||
|
||||
return BitmapDrawable(AppContext.resources, bitmap)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -26,12 +26,11 @@ object Notifications {
|
||||
|
||||
fun setup(context: Context) {
|
||||
if (SDK_INT >= 26) {
|
||||
var channel = NotificationChannel(UPDATE_NOTIFICATION_CHANNEL,
|
||||
context.getString(R.string.update_channel), NotificationManager.IMPORTANCE_DEFAULT)
|
||||
mgr.createNotificationChannel(channel)
|
||||
channel = NotificationChannel(PROGRESS_NOTIFICATION_CHANNEL,
|
||||
context.getString(R.string.progress_channel), NotificationManager.IMPORTANCE_LOW)
|
||||
mgr.createNotificationChannel(channel)
|
||||
val channel = NotificationChannel(UPDATE_NOTIFICATION_CHANNEL,
|
||||
context.getString(R.string.update_channel), NotificationManager.IMPORTANCE_DEFAULT)
|
||||
val channel2 = NotificationChannel(PROGRESS_NOTIFICATION_CHANNEL,
|
||||
context.getString(R.string.progress_channel), NotificationManager.IMPORTANCE_LOW)
|
||||
mgr.createNotificationChannels(listOf(channel, channel2))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -223,11 +223,11 @@
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
style="@style/WidgetFoundation.Card"
|
||||
gone="@{viewModel.notes.empty}"
|
||||
gone="@{viewModel.notes.length == 0}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/l1"
|
||||
android:layout_marginStart="@dimen/l1"
|
||||
android:layout_marginTop="@dimen/l1"
|
||||
android:layout_marginEnd="@dimen/l1"
|
||||
android:focusable="false">
|
||||
|
||||
@ -237,8 +237,10 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="15dp"
|
||||
android:breakStrategy="simple"
|
||||
android:hyphenationFrequency="none"
|
||||
android:textAppearance="@style/AppearanceFoundation.Caption"
|
||||
android:visibility="gone"
|
||||
tools:ignore="UnusedAttribute"
|
||||
tools:maxLines="5"
|
||||
tools:text="@tools:sample/lorem/random"
|
||||
tools:visibility="visible" />
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
@ -9,7 +10,10 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="15dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:breakStrategy="simple"
|
||||
android:hyphenationFrequency="none"
|
||||
android:paddingTop="10dp"
|
||||
android:textAppearance="@style/AppearanceFoundation.Caption" />
|
||||
android:textAppearance="@style/AppearanceFoundation.Caption"
|
||||
tools:ignore="UnusedAttribute" />
|
||||
|
||||
</ScrollView>
|
||||
</ScrollView>
|
||||
|
@ -174,7 +174,6 @@
|
||||
<string name="yes">نعم</string>
|
||||
<string name="no">لا</string>
|
||||
<string name="repo_install_title">تثبيت %1$s</string>
|
||||
<string name="repo_install_msg">هل تريد تثبيت %1$s ?</string>
|
||||
<string name="download">تنزيل</string>
|
||||
<string name="reboot">إعادة التشغيل</string>
|
||||
<string name="release_notes">معلومات الأصدار الجديد</string>
|
||||
|
@ -44,7 +44,6 @@
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="repo_install_title">%1$s faylını yüklə</string>
|
||||
<string name="repo_install_msg">%1$s faylını indi yükləmək istəyirsiniz?</string>
|
||||
<string name="download">Yüklə</string>
|
||||
<string name="reboot">Yenidən Başlat</string>
|
||||
<string name="release_notes">Yeniliklər</string>
|
||||
|
@ -172,8 +172,7 @@
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Так</string>
|
||||
<string name="no">Не</string>
|
||||
<string name="repo_install_title">Усталяваць %1$s</string>
|
||||
<string name="repo_install_msg">Усталяваць %1$s?</string>
|
||||
<string name="repo_install_title">Усталяваць %1$s %2$s(%3$s)</string>
|
||||
<string name="download">Спампаваць</string>
|
||||
<string name="reboot">Перазапуск</string>
|
||||
<string name="release_notes">Пра выпуск</string>
|
||||
|
@ -36,8 +36,7 @@
|
||||
<string name="app_changelog">Списък с промени</string>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="repo_install_title">Инсталиране на %1$s</string>
|
||||
<string name="repo_install_msg">Желаете ли да инсталирате %1$s сега?</string>
|
||||
<string name="repo_install_title">Инсталиране на %1$s %2$s(%3$s)</string>
|
||||
<string name="download">Изтегляне</string>
|
||||
<string name="reboot">Рестартиране</string>
|
||||
<string name="magisk_update_title">Достъпно е издание на Magisk.</string>
|
||||
|
@ -186,8 +186,7 @@
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Sí</string>
|
||||
<string name="no">No</string>
|
||||
<string name="repo_install_title">Instal·lar %1$s</string>
|
||||
<string name="repo_install_msg">Vol instal·lar %1$s ara?</string>
|
||||
<string name="repo_install_title">Instal·lar %1$s %2$s(%3$s)</string>
|
||||
<string name="download">Baixar</string>
|
||||
<string name="reboot">Reiniciar</string>
|
||||
<string name="release_notes">Notes de llançament</string>
|
||||
|
@ -185,8 +185,7 @@
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">ANO</string>
|
||||
<string name="no">NE</string>
|
||||
<string name="repo_install_title">Instalovat %1$s</string>
|
||||
<string name="repo_install_msg">Chcete nyní nainstalovat %1$s?</string>
|
||||
<string name="repo_install_title">Instalovat %1$s %2$s(%3$s)</string>
|
||||
<string name="download">Stáhnout</string>
|
||||
<string name="reboot">Restartovat</string>
|
||||
<string name="release_notes">Poznámky k vydání</string>
|
||||
|
@ -194,7 +194,6 @@
|
||||
<string name="yes">Ja</string>
|
||||
<string name="no">Nein</string>
|
||||
<string name="repo_install_title">%1$s installieren</string>
|
||||
<string name="repo_install_msg">Möchten Sie %1$s jetzt installieren?</string>
|
||||
<string name="download">Herunterladen</string>
|
||||
<string name="reboot">Neustart</string>
|
||||
<string name="release_notes">Anmerkungen zur Veröffentlichung</string>
|
||||
|
@ -185,8 +185,7 @@
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Ναι</string>
|
||||
<string name="no">Όχι</string>
|
||||
<string name="repo_install_title">Εγκατάσταση %1$s</string>
|
||||
<string name="repo_install_msg">Θέλετε να εγκαταστήσετε το %1$s τώρα;</string>
|
||||
<string name="repo_install_title">Εγκατάσταση %1$s %2$s(%3$s)</string>
|
||||
<string name="download">Λήψη</string>
|
||||
<string name="reboot">Επανεκκίνηση</string>
|
||||
<string name="release_notes">Σημειώσεις έκδοσης</string>
|
||||
|
@ -195,8 +195,7 @@
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Sí</string>
|
||||
<string name="no">No</string>
|
||||
<string name="repo_install_title">Instalar %1$s</string>
|
||||
<string name="repo_install_msg">¿Desea instalar %1$s ahora?</string>
|
||||
<string name="repo_install_title">Instalar %1$s %2$s(%3$s)</string>
|
||||
<string name="download">Descargar</string>
|
||||
<string name="reboot">Reiniciar</string>
|
||||
<string name="release_notes">Notas de lanzamiento</string>
|
||||
|
@ -175,8 +175,7 @@
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Jah</string>
|
||||
<string name="no">Ei</string>
|
||||
<string name="repo_install_title">Installi %1$s</string>
|
||||
<string name="repo_install_msg">Kas soovid kohe installida %1$s?</string>
|
||||
<string name="repo_install_title">Installi %1$s %2$s(%3$s)</string>
|
||||
<string name="download">Allalaadimine</string>
|
||||
<string name="reboot">Taaskäivita</string>
|
||||
<string name="release_notes">Väljalaskemärkmed</string>
|
||||
|
@ -171,7 +171,6 @@
|
||||
<string name="yes">بله</string>
|
||||
<string name="no">نه</string>
|
||||
<string name="repo_install_title">نصب کرد %1$s</string>
|
||||
<string name="repo_install_msg">آیا می خواهید %1$s نصب شود؟</string>
|
||||
<string name="download">انلود کردن</string>
|
||||
<string name="reboot">راه اندازی مجدد</string>
|
||||
<string name="release_notes">نکته های نسخه</string>
|
||||
|
@ -195,8 +195,7 @@
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Oui</string>
|
||||
<string name="no">Non</string>
|
||||
<string name="repo_install_title">Installer %1$s</string>
|
||||
<string name="repo_install_msg">Voulez‑vous installer %1$s maintenant ?</string>
|
||||
<string name="repo_install_title">Installer %1$s %2$s(%3$s)</string>
|
||||
<string name="download">Télécharger</string>
|
||||
<string name="reboot">Redémarrer</string>
|
||||
<string name="release_notes">Notes de version</string>
|
||||
|
@ -177,8 +177,7 @@
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">हाँ</string>
|
||||
<string name="no">नहीं</string>
|
||||
<string name="repo_install_title">इंस्टॉल %1$s</string>
|
||||
<string name="repo_install_msg">क्या आप अभी %1$s इंस्टॉल करना चाहते हैं?</string>
|
||||
<string name="repo_install_title">इंस्टॉल %1$s %2$s(%3$s)</string>
|
||||
<string name="download">डाउनलोड</string>
|
||||
<string name="reboot">रीबूट</string>
|
||||
<string name="release_notes">रिलीज नोट्स</string>
|
||||
|
@ -176,8 +176,7 @@
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Da</string>
|
||||
<string name="no">Ne</string>
|
||||
<string name="repo_install_title">Instaliraj %1$s</string>
|
||||
<string name="repo_install_msg">Da li želite instalirati %1$s sada?</string>
|
||||
<string name="repo_install_title">Instaliraj %1$s %2$s(%3$s)</string>
|
||||
<string name="download">Preuzmi</string>
|
||||
<string name="reboot">Ponovno pokreni</string>
|
||||
<string name="release_notes">Bilješke o izdavanju aplikacije</string>
|
||||
|
@ -180,8 +180,7 @@
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Ya</string>
|
||||
<string name="no">Tidak</string>
|
||||
<string name="repo_install_title">Instal %1$s</string>
|
||||
<string name="repo_install_msg">Apakah Anda ingin menginstal %1$s sekarang?</string>
|
||||
<string name="repo_install_title">Instal %1$s %2$s(%3$s)</string>
|
||||
<string name="download">Download</string>
|
||||
<string name="reboot">Nyalakan ulang</string>
|
||||
<string name="release_notes">Catatan rilis</string>
|
||||
|
@ -193,8 +193,7 @@
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Sì</string>
|
||||
<string name="no">No</string>
|
||||
<string name="repo_install_title">Installazione di %1$s</string>
|
||||
<string name="repo_install_msg">Vuoi installare %1$s?</string>
|
||||
<string name="repo_install_title">Installazione di %1$s %2$s(%3$s)</string>
|
||||
<string name="download">Download</string>
|
||||
<string name="reboot">Riavvia</string>
|
||||
<string name="release_notes">Note di rilascio</string>
|
||||
|
@ -183,7 +183,6 @@
|
||||
<string name="yes">כן</string>
|
||||
<string name="no">לא</string>
|
||||
<string name="repo_install_title">התקן %1$s</string>
|
||||
<string name="repo_install_msg">האם ברצונך להתקין את %1$s כעת?</string>
|
||||
<string name="download">הורדה</string>
|
||||
<string name="reboot">הפעלה מחדש</string>
|
||||
<string name="release_notes">הערות שחרור</string>
|
||||
|
@ -197,7 +197,6 @@
|
||||
<string name="yes">対応</string>
|
||||
<string name="no">非対応</string>
|
||||
<string name="repo_install_title">%1$s をインストール</string>
|
||||
<string name="repo_install_msg">%1$s をインストールしますか?</string>
|
||||
<string name="download">ダウンロード</string>
|
||||
<string name="reboot">再起動</string>
|
||||
<string name="release_notes">更新履歴</string>
|
||||
|
@ -187,7 +187,6 @@
|
||||
<string name="yes">დიახ</string>
|
||||
<string name="no">არა</string>
|
||||
<string name="repo_install_title">%1$s-ის ინსტალაცია</string>
|
||||
<string name="repo_install_msg">გნებავთ %1$s-ის ახლავე დაინსტალირება?</string>
|
||||
<string name="download">გადმოწერა</string>
|
||||
<string name="reboot">გადატვირთვა</string>
|
||||
<string name="release_notes">რელიზის შენიშვნები</string>
|
||||
|
@ -191,7 +191,6 @@
|
||||
<string name="yes">예</string>
|
||||
<string name="no">아니오</string>
|
||||
<string name="repo_install_title">%1$s 설치</string>
|
||||
<string name="repo_install_msg">정말 %1$s을(를) 설치하시겠습니까?</string>
|
||||
<string name="download">다운로드</string>
|
||||
<string name="reboot">다시 시작</string>
|
||||
<string name="release_notes">릴리즈 노트</string>
|
||||
|
@ -36,8 +36,7 @@
|
||||
<string name="app_changelog">Pakeitimų sąrašas</string>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="repo_install_title">Instaliuoti %1$s</string>
|
||||
<string name="repo_install_msg">Ar jūs norite instaliuoti %1$s?</string>
|
||||
<string name="repo_install_title">Instaliuoti %1$s %2$s(%3$s)</string>
|
||||
<string name="download">Atsisiųsti</string>
|
||||
<string name="reboot">Perkrauti</string>
|
||||
<string name="magisk_update_title">Atsirado nauja Magisk versija!</string>
|
||||
|
@ -52,8 +52,7 @@
|
||||
<string name="reboot_delay_toast">Рестартирање за 5 секунди…</string>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="repo_install_title">Инсталирај %1$s</string>
|
||||
<string name="repo_install_msg">Дали сакате да го инсталирате %1$s сега?</string>
|
||||
<string name="repo_install_title">Инсталирај %1$s %2$s(%3$s)</string>
|
||||
<string name="download">Преземи</string>
|
||||
<string name="reboot">Рестартирај</string>
|
||||
<string name="release_notes">Белешки за изданието</string>
|
||||
|
@ -186,8 +186,7 @@
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Ja</string>
|
||||
<string name="no">Nei</string>
|
||||
<string name="repo_install_title">Installer %1$s</string>
|
||||
<string name="repo_install_msg">Installer %1$s nå?</string>
|
||||
<string name="repo_install_title">Installer %1$s %2$s(%3$s)</string>
|
||||
<string name="download">Last ned</string>
|
||||
<string name="reboot">Omstart</string>
|
||||
<string name="release_notes">Utgivelsesnotater</string>
|
||||
|
@ -172,8 +172,7 @@
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Ja</string>
|
||||
<string name="no">Nee</string>
|
||||
<string name="repo_install_title">%1$s installeren</string>
|
||||
<string name="repo_install_msg">Wil je %1$s nu installeren?</string>
|
||||
<string name="repo_install_title">%1$s installeren %2$s(%3$s)</string>
|
||||
<string name="download">Downloaden</string>
|
||||
<string name="reboot">Herstarten</string>
|
||||
<string name="release_notes">Wijzigingslog</string>
|
||||
|
@ -177,8 +177,7 @@
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">ਹਾਂ</string>
|
||||
<string name="no">ਨਹੀਂ</string>
|
||||
<string name="repo_install_title">ਇੰਸਟਾਲ %1$s</string>
|
||||
<string name="repo_install_msg">ਕੀ ਤੁਸੀਂ ਹੁਣੇ %1$s ਇੰਸਟਾਲ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ?</string>
|
||||
<string name="repo_install_title">ਇੰਸਟਾਲ %1$s %2$s(%3$s)</string>
|
||||
<string name="download">ਡਾਊਨਲੋਡ</string>
|
||||
<string name="reboot">ਰੀਬੂਟ</string>
|
||||
<string name="release_notes">ਰੀਲਿਜ਼ ਨੋਟਿਸ</string>
|
||||
|
@ -186,8 +186,7 @@
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Tak</string>
|
||||
<string name="no">Nie</string>
|
||||
<string name="repo_install_title">Instalacja %1$s</string>
|
||||
<string name="repo_install_msg">Czy chcesz teraz zainstalować %1$s ?</string>
|
||||
<string name="repo_install_title">Instalacja %1$s %2$s(%3$s)</string>
|
||||
<string name="download">Pobierz</string>
|
||||
<string name="reboot">Reboot</string>
|
||||
<string name="release_notes">Lista zmian</string>
|
||||
|
@ -190,8 +190,7 @@
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Sim</string>
|
||||
<string name="no">Não</string>
|
||||
<string name="repo_install_title">Instalar %1$s</string>
|
||||
<string name="repo_install_msg">Deseja instalar %1$s agora?</string>
|
||||
<string name="repo_install_title">Instalar %1$s %2$s(%3$s)</string>
|
||||
<string name="download">Download</string>
|
||||
<string name="reboot">Reinicializar</string>
|
||||
<string name="release_notes">Notas de versão</string>
|
||||
|
@ -32,8 +32,7 @@
|
||||
<string name="app_changelog">Lista de alterações da aplicação</string>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="repo_install_title">Instalar %1$s</string>
|
||||
<string name="repo_install_msg">Deseja instalar%1$s agora?</string>
|
||||
<string name="repo_install_title">Instalar %1$s %2$s(%3$s)</string>
|
||||
<string name="download">Transferir</string>
|
||||
<string name="reboot">Reiniciar</string>
|
||||
<string name="magisk_update_title">Nova atualização do Magisk disponível!</string>
|
||||
|
@ -193,8 +193,7 @@
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Da</string>
|
||||
<string name="no">Nu</string>
|
||||
<string name="repo_install_title">Instalează %1$s</string>
|
||||
<string name="repo_install_msg">Vrei să instalezi acum %1$s?</string>
|
||||
<string name="repo_install_title">Instalează %1$s %2$s(%3$s)</string>
|
||||
<string name="download">Descarcă</string>
|
||||
<string name="reboot">Repornește</string>
|
||||
<string name="release_notes">Note privind versiunea</string>
|
||||
|
@ -196,8 +196,7 @@
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Да</string>
|
||||
<string name="no">Нет</string>
|
||||
<string name="repo_install_title">Установка %1$s</string>
|
||||
<string name="repo_install_msg">Установить %1$s ?</string>
|
||||
<string name="repo_install_title">Установка %1$s %2$s(%3$s)</string>
|
||||
<string name="download">Скачать</string>
|
||||
<string name="reboot">Перезагрузка</string>
|
||||
<string name="release_notes">О версии</string>
|
||||
|
@ -196,8 +196,7 @@
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Áno</string>
|
||||
<string name="no">Nie</string>
|
||||
<string name="repo_install_title">Nainštalovať %1$s</string>
|
||||
<string name="repo_install_msg">Chcete teraz nainštalovať %1$s?</string>
|
||||
<string name="repo_install_title">Nainštalovať %1$s %2$s(%3$s)</string>
|
||||
<string name="download">Stiahnuť</string>
|
||||
<string name="reboot">Reštartovať</string>
|
||||
<string name="release_notes">Poznámky k vydaniu</string>
|
||||
|
@ -194,8 +194,7 @@
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Po</string>
|
||||
<string name="no">Jo</string>
|
||||
<string name="repo_install_title">Instalo %1$s</string>
|
||||
<string name="repo_install_msg">Dëshiron të instalosh %1$s tani?</string>
|
||||
<string name="repo_install_title">Instalo %1$s %2$s(%3$s)</string>
|
||||
<string name="download">Shkarko</string>
|
||||
<string name="reboot">Rinis</string>
|
||||
<string name="release_notes">Shënimet e lëshimit</string>
|
||||
|
@ -34,8 +34,7 @@
|
||||
<string name="app_changelog">Дневник промена апликације</string>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="repo_install_title">Инсталирај %1$s</string>
|
||||
<string name="repo_install_msg">Да ли желите да инсталирате %1$s?</string>
|
||||
<string name="repo_install_title">Инсталирај %1$s %2$s(%3$s)</string>
|
||||
<string name="download">Преузми</string>
|
||||
<string name="reboot">Рестартуј</string>
|
||||
<string name="magisk_update_title">Нови Адбејт Магиска Доступан!</string>
|
||||
|
@ -186,8 +186,7 @@
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Ja</string>
|
||||
<string name="no">Nej</string>
|
||||
<string name="repo_install_title">Installera %1$s</string>
|
||||
<string name="repo_install_msg">Vill du installera %1$s nu?</string>
|
||||
<string name="repo_install_title">Installera %1$s %2$s(%3$s)</string>
|
||||
<string name="download">Ladda ned</string>
|
||||
<string name="reboot">Omstart</string>
|
||||
<string name="release_notes">Utgivningsanmärkningar</string>
|
||||
|
@ -186,8 +186,7 @@
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">ஆம்</string>
|
||||
<string name="no">இல்லை</string>
|
||||
<string name="repo_install_title">நிறுவு %1$s</string>
|
||||
<string name="repo_install_msg">நீங்கள் இப்போது %1$s ஐ நிறுவ விரும்புகிறீர்களா??</string>
|
||||
<string name="repo_install_title">நிறுவு %1$s %2$s(%3$s)</string>
|
||||
<string name="download">பதிவிறக்கம்</string>
|
||||
<string name="reboot">மறுதொடக்கம்</string>
|
||||
<string name="release_notes">வெளியீட்டு குறிப்புகள்</string>
|
||||
|
@ -41,8 +41,7 @@
|
||||
<string name="magisk_update_title">มีการอัพเดต Magisk!</string>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="repo_install_title">ติดตั้ง %1$s</string>
|
||||
<string name="repo_install_msg">ต้องการติดตั้ง %1$s ตอนนี้หรือไม่?</string>
|
||||
<string name="repo_install_title">ติดตั้ง %1$s %2$s(%3$s)</string>
|
||||
<string name="download">ดาวน์โหลด</string>
|
||||
<string name="reboot">รีบู๊ต</string>
|
||||
<string name="release_notes">-้อมูลเพิ่มเติม</string>
|
||||
|
@ -187,7 +187,6 @@
|
||||
<string name="yes">Evet</string>
|
||||
<string name="no">Hayır</string>
|
||||
<string name="repo_install_title">%1$s yükle</string>
|
||||
<string name="repo_install_msg">%1$s yüklensin mi?</string>
|
||||
<string name="download">İndir</string>
|
||||
<string name="reboot">Yeniden Başlat</string>
|
||||
<string name="release_notes">Sürüm notları</string>
|
||||
|
@ -193,8 +193,7 @@
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Так</string>
|
||||
<string name="no">Ні</string>
|
||||
<string name="repo_install_title">Встановити %1$s</string>
|
||||
<string name="repo_install_msg">Бажаєте встановити %1$s?</string>
|
||||
<string name="repo_install_title">Встановити %1$s %2$s(%3$s)</string>
|
||||
<string name="download">Завантажити</string>
|
||||
<string name="reboot">Перезавантажити</string>
|
||||
<string name="release_notes">Особливості версії</string>
|
||||
|
@ -193,8 +193,7 @@
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Đồng ý</string>
|
||||
<string name="no">Không</string>
|
||||
<string name="repo_install_title">Cài đặt %1$s</string>
|
||||
<string name="repo_install_msg">Bạn có muốn cài đặt %1$s ngay bây giờ không?</string>
|
||||
<string name="repo_install_title">Cài đặt %1$s %2$s(%3$s)</string>
|
||||
<string name="download">Tải xuống</string>
|
||||
<string name="reboot">Khởi động lại</string>
|
||||
<string name="release_notes">Ghi chú bản phát hành</string>
|
||||
|
@ -196,8 +196,7 @@
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">是</string>
|
||||
<string name="no">否</string>
|
||||
<string name="repo_install_title">安装 %1$s</string>
|
||||
<string name="repo_install_msg">确认安装 %1$s?</string>
|
||||
<string name="repo_install_title">安装 %1$s %2$s(%3$s)</string>
|
||||
<string name="download">下载</string>
|
||||
<string name="reboot">重启</string>
|
||||
<string name="release_notes">发布说明</string>
|
||||
|
@ -193,8 +193,7 @@
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">是</string>
|
||||
<string name="no">否</string>
|
||||
<string name="repo_install_title">安裝 %1$s</string>
|
||||
<string name="repo_install_msg">您現在想要安裝 %1$s 嗎?</string>
|
||||
<string name="repo_install_title">安裝 %1$s %2$s(%3$s)</string>
|
||||
<string name="download">下載</string>
|
||||
<string name="reboot">重新啟動</string>
|
||||
<string name="release_notes">發布說明</string>
|
||||
|
@ -1,5 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<item name="recyclerScrollListener" type="id" />
|
||||
<item name="coroutineScope" type="id" />
|
||||
</resources>
|
||||
|
@ -197,8 +197,7 @@
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Yes</string>
|
||||
<string name="no">No</string>
|
||||
<string name="repo_install_title">Install %1$s</string>
|
||||
<string name="repo_install_msg">Do you want to install %1$s now?</string>
|
||||
<string name="repo_install_title">Install %1$s %2$s(%3$s)</string>
|
||||
<string name="download">Download</string>
|
||||
<string name="reboot">Reboot</string>
|
||||
<string name="release_notes">Release notes</string>
|
||||
|
Loading…
Reference in New Issue
Block a user