diff --git a/app/build.gradle b/app/build.gradle index 8420fbaf1..2e1212c87 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -80,8 +80,9 @@ dependencies { fullImplementation "androidx.cardview:cardview:${rootProject.ext.androidXVersion}" fullImplementation "com.google.android.material:material:${rootProject.ext.androidXVersion}" fullImplementation 'com.github.topjohnwu:libsu:2.1.2' - fullImplementation 'com.atlassian.commonmark:commonmark:0.11.0' fullImplementation 'org.kamranzafar:jtar:2.3' + fullImplementation 'ru.noties:markwon:2.0.1' + fullImplementation 'com.caverock:androidsvg-aar:1.3' def butterKnifeVersion = '9.0.0-rc2' if (properties.containsKey('android.injected.invoked.from.ide')) { diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 7b07f3baa..7b0641390 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -26,6 +26,9 @@ -keepclassmembers class com.topjohnwu.core.utils.ISafetyNetHelper { *; } -keepclassmembers class com.topjohnwu.core.utils.BootSigner { *; } +# SVG +-dontwarn com.caverock.androidsvg.SVGAndroidRenderer + # Strip logging -assumenosideeffects class com.topjohnwu.core.utils.Logger { public *** debug(...); diff --git a/app/src/full/AndroidManifest.xml b/app/src/full/AndroidManifest.xml index 88133c0e1..95bc21e6d 100644 --- a/app/src/full/AndroidManifest.xml +++ b/app/src/full/AndroidManifest.xml @@ -11,6 +11,7 @@ diff --git a/app/src/full/java/com/topjohnwu/magisk/AboutActivity.java b/app/src/full/java/com/topjohnwu/magisk/AboutActivity.java index bc079bf0e..ee9a01996 100644 --- a/app/src/full/java/com/topjohnwu/magisk/AboutActivity.java +++ b/app/src/full/java/com/topjohnwu/magisk/AboutActivity.java @@ -52,8 +52,8 @@ public class AboutActivity extends BaseActivity { BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE, getPackageName())); appChangelog.setOnClickListener(v -> { - new MarkDownWindow(this, getString(R.string.app_changelog), - getResources().openRawResource(R.raw.changelog)).exec(); + MarkDownWindow.show(this, getString(R.string.app_changelog), + getResources().openRawResource(R.raw.changelog)); }); String translators = getString(R.string.translators); diff --git a/app/src/full/java/com/topjohnwu/magisk/adapters/ReposAdapter.java b/app/src/full/java/com/topjohnwu/magisk/adapters/ReposAdapter.java index 157d9098a..90d439752 100644 --- a/app/src/full/java/com/topjohnwu/magisk/adapters/ReposAdapter.java +++ b/app/src/full/java/com/topjohnwu/magisk/adapters/ReposAdapter.java @@ -101,7 +101,7 @@ public class ReposAdapter extends SectionedAdapter - new MarkDownWindow((BaseActivity) context, null, repo.getDetailUrl()).exec()); + MarkDownWindow.show((BaseActivity) context, null, repo.getDetailUrl())); holder.downloadImage.setOnClickListener(v -> { new CustomAlertDialog((BaseActivity) context) diff --git a/app/src/full/java/com/topjohnwu/magisk/components/MagiskInstallDialog.java b/app/src/full/java/com/topjohnwu/magisk/components/MagiskInstallDialog.java index 1adc9ef79..6132a3f00 100644 --- a/app/src/full/java/com/topjohnwu/magisk/components/MagiskInstallDialog.java +++ b/app/src/full/java/com/topjohnwu/magisk/components/MagiskInstallDialog.java @@ -41,7 +41,7 @@ public class MagiskInstallDialog extends CustomAlertDialog { // Open forum links in browser AppUtils.openLink(a, Uri.parse(Data.magiskNoteLink)); } else { - new MarkDownWindow(a, null, Data.magiskNoteLink).exec(); + MarkDownWindow.show(a, null, Data.magiskNoteLink); } }); } diff --git a/app/src/full/java/com/topjohnwu/magisk/components/ManagerInstallDialog.java b/app/src/full/java/com/topjohnwu/magisk/components/ManagerInstallDialog.java index 58446ee21..676a8333a 100644 --- a/app/src/full/java/com/topjohnwu/magisk/components/ManagerInstallDialog.java +++ b/app/src/full/java/com/topjohnwu/magisk/components/ManagerInstallDialog.java @@ -21,8 +21,7 @@ public class ManagerInstallDialog extends CustomAlertDialog { setPositiveButton(R.string.install, (d, i) -> DownloadApp.upgrade(name)); setNegativeButton(R.string.no_thanks, null); if (!TextUtils.isEmpty(Data.managerNoteLink)) { - setNeutralButton(R.string.app_changelog, (d, i) -> - new MarkDownWindow(a, null, Data.managerNoteLink).exec()); + setNeutralButton(R.string.app_changelog, (d, i) -> MarkDownWindow.show(a, null, Data.managerNoteLink)); } } } diff --git a/app/src/full/java/com/topjohnwu/magisk/components/MarkDownWindow.java b/app/src/full/java/com/topjohnwu/magisk/components/MarkDownWindow.java index c52fc7fba..fb803d22a 100644 --- a/app/src/full/java/com/topjohnwu/magisk/components/MarkDownWindow.java +++ b/app/src/full/java/com/topjohnwu/magisk/components/MarkDownWindow.java @@ -1,86 +1,107 @@ package com.topjohnwu.magisk.components; import android.app.Activity; -import android.webkit.WebView; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; +import android.view.LayoutInflater; +import android.view.View; +import com.caverock.androidsvg.SVG; +import com.caverock.androidsvg.SVGParseException; import com.topjohnwu.core.App; -import com.topjohnwu.core.Data; -import com.topjohnwu.core.tasks.ParallelTask; import com.topjohnwu.core.utils.Utils; import com.topjohnwu.magisk.R; +import com.topjohnwu.net.Networking; +import com.topjohnwu.net.ResponseListener; import com.topjohnwu.superuser.ShellUtils; - -import org.commonmark.node.Node; -import org.commonmark.parser.Parser; -import org.commonmark.renderer.html.HtmlRenderer; +import com.topjohnwu.utils.ByteArrayStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; +import ru.noties.markwon.Markwon; +import ru.noties.markwon.SpannableConfiguration; +import ru.noties.markwon.spans.AsyncDrawable; -public class MarkDownWindow extends ParallelTask { +public class MarkDownWindow { - private String mTitle; - private String mUrl; - private InputStream is; + private static final SpannableConfiguration config = SpannableConfiguration.builder(App.self) + .asyncDrawableLoader(new Loader()).build(); - - public MarkDownWindow(Activity context, String title, String url) { - super(context); - mTitle = title; - mUrl = url; + public static void show(Activity activity, String title, String url) { + Networking.get(url).getAsString(new Listener(activity, title)); } - public MarkDownWindow(Activity context, String title, InputStream in) { - super(context); - mTitle = title; - is = in; + public static void show (Activity activity, String title, InputStream is) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ShellUtils.pump(is, baos); + new Listener(activity, title).onResponse(baos.toString()); + } catch (IOException ignored) {} } - @Override - protected String doInBackground(Void... voids) { - App app = App.self; - String md; - if (mUrl != null) { - md = Utils.dlString(mUrl); - } else { - try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { - ShellUtils.pump(is, out); - md = out.toString(); - is.close(); - } catch (IOException e) { - e.printStackTrace(); - return ""; - } + static class Listener implements ResponseListener { + + Activity activity; + String title; + + Listener(Activity a, String t) { + activity = a; + title = t; } - String css; - try (InputStream in = app.getResources() - .openRawResource(Data.isDarkTheme ? R.raw.dark : R.raw.light); - ByteArrayOutputStream out = new ByteArrayOutputStream()) { - ShellUtils.pump(in, out); - css = out.toString(); - } catch (IOException e) { - e.printStackTrace(); - return ""; + + @Override + public void onResponse(String md) { + AlertDialog.Builder alert = new AlertDialog.Builder(activity); + alert.setTitle(title); + View mv = LayoutInflater.from(activity).inflate(R.layout.markdown_window, null); + Markwon.setMarkdown(mv.findViewById(R.id.md_txt), config, md); + alert.setView(mv); + alert.setNegativeButton(R.string.close, (dialog, id) -> dialog.dismiss()); + alert.show(); } - Parser parser = Parser.builder().build(); - HtmlRenderer renderer = HtmlRenderer.builder().build(); - Node doc = parser.parse(md); - return String.format("%s", css, renderer.render(doc)); } - @Override - protected void onPostExecute(String html) { - AlertDialog.Builder alert = new AlertDialog.Builder(getActivity()); - alert.setTitle(mTitle); + static class Loader implements AsyncDrawable.Loader { - WebView wv = new WebView(getActivity()); - wv.loadDataWithBaseURL("fake://", html, "text/html", "UTF-8", null); + @Override + public void load(@NonNull String url, @NonNull AsyncDrawable asyncDrawable) { + AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { + InputStream is = Networking.get(url).execForInputStream().getResult(); + if (is == null) + return; + ByteArrayStream buf = new ByteArrayStream(); + buf.readFrom(is); + // First try default drawables + Drawable drawable = Drawable.createFromStream(buf.getInputStream(), ""); + if (drawable == null) { + // SVG + try { + SVG svg = SVG.getFromInputStream(buf.getInputStream()); + int width = Utils.dpInPx((int) svg.getDocumentWidth()); + int height = Utils.dpInPx((int) svg.getDocumentHeight()); + final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444); + final Canvas canvas = new Canvas(bitmap); + float density = App.self.getResources().getDisplayMetrics().density; + canvas.scale(density, density); + svg.renderToCanvas(canvas); + drawable = new BitmapDrawable(App.self.getResources(), bitmap); + } catch (SVGParseException ignored) {} + } + if (drawable != null) { + drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + asyncDrawable.setResult(drawable); + } + }); + } - alert.setView(wv); - alert.setNegativeButton(R.string.close, (dialog, id) -> dialog.dismiss()); - alert.show(); + @Override + public void cancel(@NonNull String url) {} } } diff --git a/app/src/full/res/layout/markdown_window.xml b/app/src/full/res/layout/markdown_window.xml new file mode 100644 index 000000000..f5b3d43be --- /dev/null +++ b/app/src/full/res/layout/markdown_window.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/core/src/main/java/com/topjohnwu/core/tasks/ParallelTask.java b/core/src/main/java/com/topjohnwu/core/tasks/ParallelTask.java deleted file mode 100644 index a230ce136..000000000 --- a/core/src/main/java/com/topjohnwu/core/tasks/ParallelTask.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.topjohnwu.core.tasks; - -import android.app.Activity; -import android.os.AsyncTask; - -import java.lang.ref.WeakReference; - -public abstract class ParallelTask extends AsyncTask { - - private WeakReference weakActivity; - - public ParallelTask() {} - - public ParallelTask(Activity context) { - weakActivity = new WeakReference<>(context); - } - - protected Activity getActivity() { - return weakActivity.get(); - } - - @SuppressWarnings("unchecked") - public void exec(Params... params) { - executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params); - } -}