diff --git a/app/build.gradle b/app/build.gradle index 8b195a07..8c46ef88 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -76,7 +76,9 @@ dependencies { final def markwon_version = '4.6.2' implementation "io.noties.markwon:core:$markwon_version" implementation "io.noties.markwon:image:$markwon_version" + implementation "io.noties.markwon:image-glide:$markwon_version" implementation "io.noties.markwon:html:$markwon_version" + implementation "io.noties.markwon:linkify:$markwon_version" implementation 'rikka.insets:insets:1.0.1' implementation 'rikka.recyclerview:recyclerview-utils:1.2.0' implementation "rikka.widget:switchbar:1.0.2" diff --git a/app/src/main/java/io/github/lsposed/manager/ui/activity/RepoActivity.java b/app/src/main/java/io/github/lsposed/manager/ui/activity/RepoActivity.java index 2b6adff3..3ca11552 100644 --- a/app/src/main/java/io/github/lsposed/manager/ui/activity/RepoActivity.java +++ b/app/src/main/java/io/github/lsposed/manager/ui/activity/RepoActivity.java @@ -34,26 +34,17 @@ import androidx.appcompat.app.ActionBar; import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.RecyclerView; -import com.google.gson.Gson; - -import java.io.IOException; - -import io.github.lsposed.manager.App; import io.github.lsposed.manager.R; import io.github.lsposed.manager.databinding.ActivityAppListBinding; import io.github.lsposed.manager.repo.RepoLoader; import io.github.lsposed.manager.repo.model.OnlineModule; import io.github.lsposed.manager.util.LinearLayoutManagerFix; import me.zhanghai.android.fastscroll.FastScrollerBuilder; -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.Request; -import okhttp3.Response; public class RepoActivity extends BaseActivity implements RepoLoader.Listener { + private final RepoLoader repoLoader = RepoLoader.getInstance(); private ActivityAppListBinding binding; private RepoAdapter adapter; - private RepoLoader repoLoader = RepoLoader.getInstance(); @Override public void onCreate(@Nullable Bundle savedInstanceState) { @@ -80,15 +71,14 @@ public class RepoActivity extends BaseActivity implements RepoLoader.Listener { } repoLoader.addListener(this); fastScrollerBuilder.build(); - binding.swipeRefreshLayout.setOnRefreshListener(() -> { - repoLoader.loadRemoteData(); - }); + binding.swipeRefreshLayout.setOnRefreshListener(repoLoader::loadRemoteData); } @Override protected void onResume() { super.onResume(); adapter.setData(repoLoader.getOnlineModules()); + binding.swipeRefreshLayout.setRefreshing(adapter.getItemCount() == 0); } @Override diff --git a/app/src/main/java/io/github/lsposed/manager/ui/activity/RepoItemActivity.java b/app/src/main/java/io/github/lsposed/manager/ui/activity/RepoItemActivity.java index 238fd775..90757f99 100644 --- a/app/src/main/java/io/github/lsposed/manager/ui/activity/RepoItemActivity.java +++ b/app/src/main/java/io/github/lsposed/manager/ui/activity/RepoItemActivity.java @@ -22,6 +22,7 @@ package io.github.lsposed.manager.ui.activity; import android.os.Build; import android.os.Bundle; +import android.text.util.Linkify; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; @@ -49,9 +50,13 @@ import io.github.lsposed.manager.databinding.ItemRepoReleaseBinding; import io.github.lsposed.manager.databinding.ItemRepoReleasesBinding; import io.github.lsposed.manager.repo.model.OnlineModule; import io.github.lsposed.manager.repo.model.Release; +import io.github.lsposed.manager.util.GlideApp; import io.github.lsposed.manager.util.LinearLayoutManagerFix; import io.github.lsposed.manager.util.NavUtil; +import io.github.lsposed.manager.util.chrome.LinkTransformationMethod; import io.noties.markwon.Markwon; +import io.noties.markwon.image.glide.GlideImagesPlugin; +import io.noties.markwon.linkify.LinkifyPlugin; public class RepoItemActivity extends BaseActivity { ActivityModuleDetailBinding binding; @@ -71,7 +76,10 @@ public class RepoItemActivity extends BaseActivity { bar.setTitle(module.getDescription()); bar.setSubtitle(module.getName()); bar.setDisplayHomeAsUpEnabled(true); - markwon = Markwon.create(this); + markwon = Markwon.builder(this) + .usePlugin(GlideImagesPlugin.create(GlideApp.with(this))) + .usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS)) + .build(); binding.viewPager.setAdapter(new PagerAdapter()); binding.viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { @Override @@ -83,7 +91,7 @@ public class RepoItemActivity extends BaseActivity { } } }); - int[] titles = new int[]{R.string.module_readme, R.string.module_readme}; + int[] titles = new int[]{R.string.module_readme, R.string.module_releases}; new TabLayoutMediator(binding.tabLayout, binding.viewPager, (tab, position) -> tab.setText(titles[position])).attach(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { WindowCompat.setDecorFitsSystemWindows(getWindow(), false); @@ -183,6 +191,7 @@ public class RepoItemActivity extends BaseActivity { } if (position == 0) { binding.appBar.setLiftOnScrollTargetViewId(R.id.scrollView); + holder.textView.setTransformationMethod(new LinkTransformationMethod(RepoItemActivity.this)); markwon.setMarkdown(holder.textView, module.getReadme()); } else { binding.appBar.setLiftOnScrollTargetViewId(R.id.recyclerView); diff --git a/app/src/main/java/io/github/lsposed/manager/util/IconLoader.java b/app/src/main/java/io/github/lsposed/manager/util/AppModule.java similarity index 71% rename from app/src/main/java/io/github/lsposed/manager/util/IconLoader.java rename to app/src/main/java/io/github/lsposed/manager/util/AppModule.java index 8b4e1edd..0a30b886 100644 --- a/app/src/main/java/io/github/lsposed/manager/util/IconLoader.java +++ b/app/src/main/java/io/github/lsposed/manager/util/AppModule.java @@ -10,19 +10,26 @@ import androidx.annotation.NonNull; import com.bumptech.glide.Glide; import com.bumptech.glide.Registry; import com.bumptech.glide.annotation.GlideModule; +import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader; +import com.bumptech.glide.load.model.GlideUrl; import com.bumptech.glide.module.AppGlideModule; +import java.io.InputStream; + +import io.github.lsposed.manager.App; import io.github.lsposed.manager.R; import me.zhanghai.android.appiconloader.glide.AppIconModelLoader; @GlideModule -public class IconLoader extends AppGlideModule { +public class AppModule extends AppGlideModule { @Override public void registerComponents(Context context, @NonNull Glide glide, Registry registry) { int iconSize = context.getResources().getDimensionPixelSize(R.dimen.app_icon_size); registry.prepend(PackageInfo.class, Bitmap.class, new AppIconModelLoader.Factory(iconSize, context.getApplicationInfo().loadIcon(context.getPackageManager()) instanceof AdaptiveIconDrawable, context)); + OkHttpUrlLoader.Factory factory = new OkHttpUrlLoader.Factory(App.getOkHttpClient()); + registry.prepend(GlideUrl.class, InputStream.class, factory); } } diff --git a/app/src/main/java/io/github/lsposed/manager/util/chrome/CustomTabsURLSpan.java b/app/src/main/java/io/github/lsposed/manager/util/chrome/CustomTabsURLSpan.java new file mode 100644 index 00000000..faf7b516 --- /dev/null +++ b/app/src/main/java/io/github/lsposed/manager/util/chrome/CustomTabsURLSpan.java @@ -0,0 +1,43 @@ +/* + * This file is part of LSPosed. + * + * LSPosed is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LSPosed is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LSPosed. If not, see . + * + * Copyright (C) 2020 EdXposed Contributors + * Copyright (C) 2021 LSPosed Contributors + */ + +package io.github.lsposed.manager.util.chrome; + +import android.text.style.URLSpan; +import android.view.View; + +import io.github.lsposed.manager.ui.activity.BaseActivity; +import io.github.lsposed.manager.util.NavUtil; + +public class CustomTabsURLSpan extends URLSpan { + + private final BaseActivity activity; + + CustomTabsURLSpan(BaseActivity activity, String url) { + super(url); + this.activity = activity; + } + + @Override + public void onClick(View widget) { + String url = getURL(); + NavUtil.startURL(activity, url); + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/lsposed/manager/util/chrome/LinkTransformationMethod.java b/app/src/main/java/io/github/lsposed/manager/util/chrome/LinkTransformationMethod.java new file mode 100644 index 00000000..ad2f6506 --- /dev/null +++ b/app/src/main/java/io/github/lsposed/manager/util/chrome/LinkTransformationMethod.java @@ -0,0 +1,66 @@ +/* + * This file is part of LSPosed. + * + * LSPosed is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LSPosed is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LSPosed. If not, see . + * + * Copyright (C) 2020 EdXposed Contributors + * Copyright (C) 2021 LSPosed Contributors + */ + +package io.github.lsposed.manager.util.chrome; + +import android.graphics.Rect; +import android.text.Spannable; +import android.text.Spanned; +import android.text.method.TransformationMethod; +import android.text.style.URLSpan; +import android.view.View; +import android.widget.TextView; + +import io.github.lsposed.manager.ui.activity.BaseActivity; + +public class LinkTransformationMethod implements TransformationMethod { + + private final BaseActivity activity; + + public LinkTransformationMethod(BaseActivity activity) { + this.activity = activity; + } + + @Override + public CharSequence getTransformation(CharSequence source, View view) { + if (view instanceof TextView) { + TextView textView = (TextView) view; + if (textView.getText() == null || !(textView.getText() instanceof Spannable)) { + return source; + } + Spannable text = (Spannable) textView.getText(); + URLSpan[] spans = text.getSpans(0, textView.length(), URLSpan.class); + for (int i = spans.length - 1; i >= 0; i--) { + URLSpan oldSpan = spans[i]; + int start = text.getSpanStart(oldSpan); + int end = text.getSpanEnd(oldSpan); + String url = oldSpan.getURL(); + text.removeSpan(oldSpan); + text.setSpan(new CustomTabsURLSpan(activity, url), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + return text; + } + return source; + } + + @Override + public void onFocusChanged(View view, CharSequence sourceText, boolean focused, int direction, Rect previouslyFocusedRect) { + } +} \ No newline at end of file