mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-12-25 13:07:39 +00:00
parent
d8b1d79879
commit
ac2a9da4c4
@ -0,0 +1,244 @@
|
||||
package com.topjohnwu.magisk.core.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.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.core.view.marginLeft
|
||||
import androidx.core.view.marginRight
|
||||
import com.caverock.androidsvg.SVG
|
||||
import com.caverock.androidsvg.SVGParseException
|
||||
import com.topjohnwu.magisk.core.ResMgr
|
||||
import com.topjohnwu.superuser.internal.WaitRunnable
|
||||
import io.noties.markwon.*
|
||||
import io.noties.markwon.image.*
|
||||
import io.noties.markwon.image.data.DataUriSchemeHandler
|
||||
import io.noties.markwon.image.network.OkHttpNetworkSchemeHandler
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.runBlocking
|
||||
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, ImageSpanFactory())
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
// Download and create all drawables in parallel
|
||||
runBlocking(Dispatchers.IO) {
|
||||
// Download and decode all drawables in parallel
|
||||
val defers = spans.map { async { it.load() } }
|
||||
|
||||
// Get TextView sizes beforehand to resize all images
|
||||
// We get its parent here as the TextView itself might be hidden
|
||||
val parent = tv.parent as View
|
||||
parent.post(WaitRunnable{
|
||||
// Make sure it is rendered
|
||||
val width = parent.width -
|
||||
tv.paddingLeft - tv.paddingRight -
|
||||
tv.marginLeft - tv.marginRight
|
||||
spans.forEach { it.canvasWidth = width }
|
||||
})
|
||||
|
||||
// Make sure all is done before returning
|
||||
defers.awaitAll()
|
||||
}
|
||||
}
|
||||
|
||||
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 ImageSpanFactory : SpanFactory {
|
||||
override fun getSpans(configuration: MarkwonConfiguration, props: RenderProps): Any? {
|
||||
val dest = ImageProps.DESTINATION.require(props)
|
||||
val size = ImageProps.IMAGE_SIZE.get(props)
|
||||
return ImageSpan(dest, size)
|
||||
}
|
||||
}
|
||||
|
||||
inner class ImageSpan(
|
||||
private val dest: String,
|
||||
private val size: ImageSize?
|
||||
) : DynamicDrawableSpan(ALIGN_BOTTOM) {
|
||||
|
||||
var canvasWidth = 0
|
||||
var measured = false
|
||||
lateinit var draw: Drawable
|
||||
|
||||
fun load() {
|
||||
draw = loadDrawable(dest) ?: ShapeDrawable()
|
||||
}
|
||||
|
||||
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)
|
||||
return
|
||||
if (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: SVG
|
||||
svg = try {
|
||||
SVG.getFromInputStream(inputStream)
|
||||
} catch (e: SVGParseException) {
|
||||
throw IllegalStateException("Exception decoding SVG", e)
|
||||
}
|
||||
|
||||
val w = svg.documentWidth
|
||||
val h = svg.documentHeight
|
||||
|
||||
if (w == 0f || h == 0f) {
|
||||
val picture = svg.renderToPicture()
|
||||
return PictureDrawable(picture)
|
||||
}
|
||||
|
||||
val density: Float = ResMgr.resource.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(ResMgr.resource, bitmap)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@ package com.topjohnwu.magisk.core.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Resources
|
||||
import android.net.Uri
|
||||
import android.os.Environment
|
||||
import android.widget.Toast
|
||||
@ -24,11 +23,6 @@ object Utils {
|
||||
UiThreadHandler.run { Toast.makeText(get(), resId, duration).show() }
|
||||
}
|
||||
|
||||
fun dpInPx(dp: Int): Int {
|
||||
val scale = get<Resources>().displayMetrics.density
|
||||
return (dp * scale + 0.5).toInt()
|
||||
}
|
||||
|
||||
fun showSuperUser(): Boolean {
|
||||
return Info.env.isActive && (Const.USER_ID == 0
|
||||
|| Config.suMultiuserMode != Config.Value.MULTIUSER_MODE_OWNER_MANAGED)
|
||||
|
@ -2,15 +2,13 @@ package com.topjohnwu.magisk.databinding
|
||||
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.core.text.PrecomputedTextCompat
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.widget.TextViewCompat
|
||||
import androidx.databinding.BindingAdapter
|
||||
import com.topjohnwu.magisk.ktx.coroutineScope
|
||||
import com.topjohnwu.magisk.ktx.get
|
||||
import io.noties.markwon.Markwon
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@BindingAdapter("gone")
|
||||
@ -33,18 +31,10 @@ fun setInvisibleUnless(view: View, invisibleUnless: Boolean) {
|
||||
setInvisible(view, invisibleUnless.not())
|
||||
}
|
||||
|
||||
@BindingAdapter("precomputedText")
|
||||
fun setPrecomputedText(tv: TextView, text: CharSequence) {
|
||||
GlobalScope.launch(Dispatchers.Default) {
|
||||
val pre = PrecomputedTextCompat.create(text, TextViewCompat.getTextMetricsParams(tv))
|
||||
tv.post {
|
||||
TextViewCompat.setPrecomputedText(tv, pre);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@BindingAdapter("markdownText")
|
||||
fun setMarkdownText(tv: TextView, text: CharSequence) {
|
||||
val markwon = get<Markwon>()
|
||||
markwon.setMarkdown(tv, text.toString())
|
||||
tv.coroutineScope.launch(Dispatchers.IO) {
|
||||
val markwon = get<Markwon>()
|
||||
markwon.setMarkdown(tv, text.toString())
|
||||
}
|
||||
}
|
||||
|
@ -5,15 +5,14 @@ import android.os.Build
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.utils.MarkwonImagePlugin
|
||||
import com.topjohnwu.magisk.data.network.GithubApiServices
|
||||
import com.topjohnwu.magisk.data.network.GithubRawServices
|
||||
import com.topjohnwu.magisk.ktx.precomputedText
|
||||
import com.topjohnwu.magisk.net.Networking
|
||||
import com.topjohnwu.magisk.net.NoSSLv3SocketFactory
|
||||
import com.topjohnwu.magisk.view.PrecomputedTextSetter
|
||||
import io.noties.markwon.Markwon
|
||||
import io.noties.markwon.html.HtmlPlugin
|
||||
import io.noties.markwon.image.ImagesPlugin
|
||||
import io.noties.markwon.image.network.OkHttpNetworkSchemeHandler
|
||||
import okhttp3.Dns
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
@ -96,10 +95,11 @@ inline fun <reified T> createApiService(retrofitBuilder: Retrofit.Builder, baseU
|
||||
|
||||
fun createMarkwon(context: Context, okHttpClient: OkHttpClient): Markwon {
|
||||
return Markwon.builder(context)
|
||||
.textSetter(PrecomputedTextSetter())
|
||||
.textSetter { textView, spanned, _, onComplete ->
|
||||
textView.tag = onComplete
|
||||
textView.precomputedText = spanned
|
||||
}
|
||||
.usePlugin(HtmlPlugin.create())
|
||||
.usePlugin(ImagesPlugin.create {
|
||||
it.addSchemeHandler(OkHttpNetworkSchemeHandler.create(okHttpClient))
|
||||
})
|
||||
.usePlugin(MarkwonImagePlugin(okHttpClient))
|
||||
.build()
|
||||
}
|
||||
|
@ -22,8 +22,12 @@ import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Build.VERSION.SDK_INT
|
||||
import android.provider.OpenableColumns
|
||||
import android.text.PrecomputedText
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewTreeObserver
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
@ -31,13 +35,26 @@ import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.net.toFile
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.text.PrecomputedTextCompat
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.widget.TextViewCompat
|
||||
import androidx.databinding.BindingAdapter
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||
import androidx.transition.AutoTransition
|
||||
import androidx.transition.TransitionManager
|
||||
import com.topjohnwu.magisk.FileProvider
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.ResMgr
|
||||
import com.topjohnwu.magisk.core.utils.Utils
|
||||
import com.topjohnwu.magisk.core.utils.currentLocale
|
||||
import com.topjohnwu.magisk.utils.DynamicClassLoader
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.lang.reflect.Array as JArray
|
||||
@ -328,3 +345,78 @@ fun Activity.hideKeyboard() {
|
||||
fun Fragment.hideKeyboard() {
|
||||
activity?.hideKeyboard()
|
||||
}
|
||||
|
||||
fun View.setOnViewReadyListener(callback: () -> Unit) = addOnGlobalLayoutListener(true, callback)
|
||||
|
||||
fun View.addOnGlobalLayoutListener(oneShot: Boolean = false, callback: () -> Unit) =
|
||||
viewTreeObserver.addOnGlobalLayoutListener(object :
|
||||
ViewTreeObserver.OnGlobalLayoutListener {
|
||||
override fun onGlobalLayout() {
|
||||
if (oneShot) viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||
callback()
|
||||
}
|
||||
})
|
||||
|
||||
fun ViewGroup.startAnimations() {
|
||||
val transition = AutoTransition()
|
||||
.setInterpolator(FastOutSlowInInterpolator()).setDuration(400)
|
||||
TransitionManager.beginDelayedTransition(
|
||||
this,
|
||||
transition
|
||||
)
|
||||
}
|
||||
|
||||
var View.coroutineScope: CoroutineScope
|
||||
get() = getTag(R.id.coroutineScope) as? CoroutineScope ?: GlobalScope
|
||||
set(value) = setTag(R.id.coroutineScope, value)
|
||||
|
||||
@set:BindingAdapter("precomputedText")
|
||||
var TextView.precomputedText: CharSequence
|
||||
get() = text
|
||||
set(value) {
|
||||
val callback = tag as? Runnable
|
||||
|
||||
// Don't even bother pre 21
|
||||
if (SDK_INT < 21) {
|
||||
post {
|
||||
text = value
|
||||
isGone = false
|
||||
callback?.run()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
if (SDK_INT >= 29) {
|
||||
// Internally PrecomputedTextCompat will 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 = ResMgr.resource.displayMetrics.density
|
||||
return (this * scale + 0.5).toInt()
|
||||
}
|
||||
|
@ -1,23 +0,0 @@
|
||||
package com.topjohnwu.magisk.ktx
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewTreeObserver
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||
import androidx.transition.AutoTransition
|
||||
import androidx.transition.TransitionManager
|
||||
|
||||
fun View.setOnViewReadyListener(callback: () -> Unit) = addOnGlobalLayoutListener(true, callback)
|
||||
|
||||
fun View.addOnGlobalLayoutListener(oneShot: Boolean = false, callback: () -> Unit) =
|
||||
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
|
||||
override fun onGlobalLayout() {
|
||||
if (oneShot) viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||
callback()
|
||||
}
|
||||
})
|
||||
|
||||
fun ViewGroup.startAnimations() {
|
||||
val transition = AutoTransition().setInterpolator(FastOutSlowInInterpolator()).setDuration(400)
|
||||
TransitionManager.beginDelayedTransition(this, transition)
|
||||
}
|
@ -1,34 +1,21 @@
|
||||
package com.topjohnwu.magisk.view
|
||||
|
||||
import android.content.Context
|
||||
import android.text.Spanned
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.TextView
|
||||
import androidx.core.text.PrecomputedTextCompat
|
||||
import androidx.core.widget.TextViewCompat
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.data.repository.StringRepository
|
||||
import com.topjohnwu.magisk.ktx.coroutineScope
|
||||
import io.noties.markwon.Markwon
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
import timber.log.Timber
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
class PrecomputedTextSetter : Markwon.TextSetter {
|
||||
|
||||
override fun setText(tv: TextView, text: Spanned, bufferType: TextView.BufferType, onComplete: Runnable) {
|
||||
val scope = tv.tag as? CoroutineScope ?: GlobalScope
|
||||
scope.launch(Dispatchers.Default) {
|
||||
val pre = PrecomputedTextCompat.create(text, TextViewCompat.getTextMetricsParams(tv))
|
||||
tv.post {
|
||||
TextViewCompat.setPrecomputedText(tv, pre)
|
||||
onComplete.run()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object MarkDownWindow : KoinComponent {
|
||||
|
||||
private val repo: StringRepository by inject()
|
||||
@ -41,26 +28,27 @@ object MarkDownWindow : KoinComponent {
|
||||
}
|
||||
|
||||
suspend fun show(activity: Context, title: String?, input: suspend () -> String) {
|
||||
val mdRes = R.layout.markdown_window_md2
|
||||
val mv = LayoutInflater.from(activity).inflate(mdRes, null)
|
||||
val tv = mv.findViewById<TextView>(R.id.md_txt)
|
||||
tv.tag = CoroutineScope(coroutineContext)
|
||||
|
||||
try {
|
||||
markwon.setMarkdown(tv, input())
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException)
|
||||
throw e
|
||||
Timber.e(e)
|
||||
tv.setText(R.string.download_file_error)
|
||||
}
|
||||
val view = LayoutInflater.from(activity).inflate(R.layout.markdown_window_md2, null)
|
||||
|
||||
MagiskDialog(activity)
|
||||
.applyTitle(title ?: "")
|
||||
.applyView(mv)
|
||||
.applyView(view)
|
||||
.applyButton(MagiskDialog.ButtonType.NEGATIVE) {
|
||||
titleRes = android.R.string.cancel
|
||||
}
|
||||
.reveal()
|
||||
|
||||
val tv = view.findViewById<TextView>(R.id.md_txt)
|
||||
tv.coroutineScope = CoroutineScope(coroutineContext)
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
markwon.setMarkdown(tv, input())
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException)
|
||||
throw e
|
||||
Timber.e(e)
|
||||
tv.setText(R.string.download_file_error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -233,6 +233,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="15dp"
|
||||
android:textAppearance="@style/AppearanceFoundation.Caption"
|
||||
android:visibility="gone"
|
||||
markdownText="@{viewModel.notes}"/>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
@ -2,5 +2,6 @@
|
||||
<resources>
|
||||
|
||||
<item name="recyclerScrollListener" type="id" />
|
||||
<item name="coroutineScope" type="id"/>
|
||||
|
||||
</resources>
|
||||
</resources>
|
||||
|
Loading…
x
Reference in New Issue
Block a user