From 2a6afa87836b6385a23093200804ef1c9e55d3c3 Mon Sep 17 00:00:00 2001 From: Alex Ning Date: Mon, 2 Aug 2021 17:12:33 +0800 Subject: Support viewing subreddit wiki in app. --- .../infinityforreddit/AppComponent.java | 3 + .../activities/FullMarkdownActivity.java | 23 +- .../activities/LinkResolverActivity.java | 5 + .../infinityforreddit/activities/WikiActivity.java | 322 +++++++++++++++++++++ .../infinityforreddit/apis/RedditAPI.java | 3 + .../infinityforreddit/utils/JSONUtils.java | 1 + 6 files changed, 353 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/activities/WikiActivity.java (limited to 'app/src/main/java/ml') diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java b/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java index 94b55f15..47c27b13 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java @@ -59,6 +59,7 @@ import ml.docilealligator.infinityforreddit.activities.ViewSubredditDetailActivi import ml.docilealligator.infinityforreddit.activities.ViewUserDetailActivity; import ml.docilealligator.infinityforreddit.activities.ViewVideoActivity; import ml.docilealligator.infinityforreddit.activities.WebViewActivity; +import ml.docilealligator.infinityforreddit.activities.WikiActivity; import ml.docilealligator.infinityforreddit.bottomsheetfragments.FlairBottomSheetFragment; import ml.docilealligator.infinityforreddit.fragments.CommentsListingFragment; import ml.docilealligator.infinityforreddit.fragments.FollowedUsersListingFragment; @@ -281,4 +282,6 @@ public interface AppComponent { void inject(PostGalleryActivity postGalleryActivity); void inject(TrendingActivity trendingActivity); + + void inject(WikiActivity wikiActivity); } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/FullMarkdownActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/FullMarkdownActivity.java index 8ed8fa5e..4045826b 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/FullMarkdownActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/FullMarkdownActivity.java @@ -26,11 +26,12 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.appbar.AppBarLayout; -import com.google.android.material.appbar.CollapsingToolbarLayout; import com.r0adkll.slidr.Slidr; import com.r0adkll.slidr.model.SlidrInterface; import org.commonmark.ext.gfm.tables.TableBlock; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -54,6 +55,7 @@ import ml.docilealligator.infinityforreddit.Infinity; import ml.docilealligator.infinityforreddit.R; import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper; import ml.docilealligator.infinityforreddit.customviews.MarkwonLinearLayoutManager; +import ml.docilealligator.infinityforreddit.events.SwitchAccountEvent; import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; public class FullMarkdownActivity extends BaseActivity { @@ -66,8 +68,6 @@ public class FullMarkdownActivity extends BaseActivity { CoordinatorLayout coordinatorLayout; @BindView(R.id.appbar_layout_comment_full_markdown_activity) AppBarLayout appBarLayout; - @BindView(R.id.collapsing_toolbar_layout_comment_full_markdown_activity) - CollapsingToolbarLayout collapsingToolbarLayout; @BindView(R.id.toolbar_comment_full_markdown_activity) Toolbar toolbar; @BindView(R.id.content_markdown_view_comment_full_markdown_activity) @@ -88,6 +88,8 @@ public class FullMarkdownActivity extends BaseActivity { ButterKnife.bind(this); + EventBus.getDefault().register(this); + applyCustomTheme(); setSupportActionBar(toolbar); @@ -115,7 +117,7 @@ public class FullMarkdownActivity extends BaseActivity { window.setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); } adjustToolbar(toolbar); - markdownRecyclerView.setPadding(0, 0, 0, getNavBarHeight()); + markdownRecyclerView.setPadding(markdownRecyclerView.getPaddingLeft(), 0, markdownRecyclerView.getPaddingRight(), getNavBarHeight()); } } @@ -277,4 +279,17 @@ public class FullMarkdownActivity extends BaseActivity { coordinatorLayout.setBackgroundColor(mCustomThemeWrapper.getBackgroundColor()); applyAppBarLayoutAndToolbarTheme(appBarLayout, toolbar); } + + @Override + protected void onDestroy() { + super.onDestroy(); + EventBus.getDefault().unregister(this); + } + + @Subscribe + public void onAccountSwitchEvent(SwitchAccountEvent event) { + if (!getClass().getName().equals(event.excludeActivityClassName)) { + finish(); + } + } } \ No newline at end of file diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/LinkResolverActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/LinkResolverActivity.java index e0154df2..886f0d45 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/LinkResolverActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/LinkResolverActivity.java @@ -49,6 +49,7 @@ public class LinkResolverActivity extends AppCompatActivity { private static final String IMGUR_ALBUM_PATTERN = "/(album|a)/\\w+/?"; private static final String IMGUR_IMAGE_PATTERN = "/\\w+/?"; private static final String RPAN_BROADCAST_PATTERN = "/rpan/r/[\\w-]+/\\w+/?\\w+/?"; + private static final String WIKI_PATTERN = "/[rR]/[\\w-]+/wiki/?\\w+"; @Inject @Named("default") @@ -171,6 +172,10 @@ public class LinkResolverActivity extends AppCompatActivity { } else { deepLinkError(uri); } + } else if (path.matches(WIKI_PATTERN)) { + Intent intent = new Intent(this, WikiActivity.class); + intent.putExtra(WikiActivity.EXTRA_SUBREDDIT_NAME, segments.get(1)); + startActivity(intent); } else if (path.matches(SUBREDDIT_PATTERN)) { Intent intent = new Intent(this, ViewSubredditDetailActivity.class); intent.putExtra(ViewSubredditDetailActivity.EXTRA_SUBREDDIT_NAME_KEY, path.substring(3)); diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/WikiActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/WikiActivity.java new file mode 100644 index 00000000..492553dc --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/WikiActivity.java @@ -0,0 +1,322 @@ +package ml.docilealligator.infinityforreddit.activities; + +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Color; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextPaint; +import android.text.style.ClickableSpan; +import android.text.util.Linkify; +import android.view.MenuItem; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.appcompat.widget.Toolbar; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.appbar.AppBarLayout; +import com.r0adkll.slidr.Slidr; +import com.r0adkll.slidr.model.SlidrInterface; + +import org.commonmark.ext.gfm.tables.TableBlock; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.inject.Inject; +import javax.inject.Named; + +import butterknife.BindView; +import butterknife.ButterKnife; +import io.noties.markwon.AbstractMarkwonPlugin; +import io.noties.markwon.Markwon; +import io.noties.markwon.MarkwonConfiguration; +import io.noties.markwon.core.MarkwonTheme; +import io.noties.markwon.ext.strikethrough.StrikethroughPlugin; +import io.noties.markwon.html.HtmlPlugin; +import io.noties.markwon.linkify.LinkifyPlugin; +import io.noties.markwon.recycler.MarkwonAdapter; +import io.noties.markwon.recycler.table.TableEntry; +import io.noties.markwon.recycler.table.TableEntryPlugin; +import ml.docilealligator.infinityforreddit.Infinity; +import ml.docilealligator.infinityforreddit.R; +import ml.docilealligator.infinityforreddit.apis.RedditAPI; +import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper; +import ml.docilealligator.infinityforreddit.customviews.MarkwonLinearLayoutManager; +import ml.docilealligator.infinityforreddit.events.SwitchAccountEvent; +import ml.docilealligator.infinityforreddit.utils.JSONUtils; +import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; + +public class WikiActivity extends BaseActivity { + + public static final String EXTRA_SUBREDDIT_NAME = "ESN"; + private static final String WIKI_MARKDOWN_STATE = "WMS"; + + @BindView(R.id.coordinator_layout_comment_wiki_activity) + CoordinatorLayout coordinatorLayout; + @BindView(R.id.appbar_layout_comment_wiki_activity) + AppBarLayout appBarLayout; + @BindView(R.id.toolbar_comment_wiki_activity) + Toolbar toolbar; + @BindView(R.id.content_markdown_view_comment_wiki_activity) + RecyclerView markdownRecyclerView; + @Inject + @Named("no_oauth") + Retrofit retrofit; + @Inject + @Named("default") + SharedPreferences mSharedPreferences; + @Inject + CustomThemeWrapper mCustomThemeWrapper; + private SlidrInterface mSlidrInterface; + private String wikiMarkdown; + private Markwon markwon; + private MarkwonAdapter markwonAdapter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + ((Infinity) getApplication()).getAppComponent().inject(this); + + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_wiki); + + ButterKnife.bind(this); + + EventBus.getDefault().register(this); + + applyCustomTheme(); + + setSupportActionBar(toolbar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + if (mSharedPreferences.getBoolean(SharedPreferencesUtils.SWIPE_RIGHT_TO_GO_BACK, true)) { + mSlidrInterface = Slidr.attach(this); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Window window = getWindow(); + + if (isChangeStatusBarIconColor()) { + addOnOffsetChangedListener(appBarLayout); + } + + if (isImmersiveInterface()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + coordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + } else { + window.setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); + } + adjustToolbar(toolbar); + markdownRecyclerView.setPadding(markdownRecyclerView.getPaddingLeft(), 0, markdownRecyclerView.getPaddingRight(), getNavBarHeight()); + } + } + + int markdownColor = mCustomThemeWrapper.getPrimaryTextColor(); + int spoilerBackgroundColor = markdownColor | 0xFF000000; + int linkColor = mCustomThemeWrapper.getLinkColor(); + markwon = Markwon.builder(this) + .usePlugin(HtmlPlugin.create()) + .usePlugin(new AbstractMarkwonPlugin() { + @NonNull + @Override + public String processMarkdown(@NonNull String markdown) { + StringBuilder markdownStringBuilder = new StringBuilder(markdown); + Pattern spoilerPattern = Pattern.compile(">![\\S\\s]*?!<"); + Matcher matcher = spoilerPattern.matcher(markdownStringBuilder); + while (matcher.find()) { + markdownStringBuilder.replace(matcher.start(), matcher.start() + 1, ">"); + } + return super.processMarkdown(markdownStringBuilder.toString()); + } + + @Override + public void afterSetText(@NonNull TextView textView) { + textView.setHighlightColor(Color.TRANSPARENT); + SpannableStringBuilder markdownStringBuilder = new SpannableStringBuilder(textView.getText().toString()); + Pattern spoilerPattern = Pattern.compile(">![\\S\\s]*?!<"); + Matcher matcher = spoilerPattern.matcher(markdownStringBuilder); + int start = 0; + boolean find = false; + while (matcher.find(start)) { + if (markdownStringBuilder.length() < 4 + || matcher.start() < 0 + || matcher.end() > markdownStringBuilder.length()) { + break; + } + find = true; + markdownStringBuilder.delete(matcher.end() - 2, matcher.end()); + markdownStringBuilder.delete(matcher.start(), matcher.start() + 2); + ClickableSpan clickableSpan = new ClickableSpan() { + private boolean isShowing = false; + @Override + public void updateDrawState(@NonNull TextPaint ds) { + if (isShowing) { + super.updateDrawState(ds); + ds.setColor(markdownColor); + } else { + ds.bgColor = spoilerBackgroundColor; + ds.setColor(markdownColor); + } + ds.setUnderlineText(false); + } + + @Override + public void onClick(@NonNull View view) { + isShowing = !isShowing; + view.invalidate(); + } + }; + markdownStringBuilder.setSpan(clickableSpan, matcher.start(), matcher.end() - 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + start = matcher.end() - 4; + } + if (find) { + textView.setText(markdownStringBuilder); + } + } + + @Override + public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) { + textView.setTextColor(markdownColor); + } + + @Override + public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { + builder.linkResolver((view, link) -> { + Intent intent = new Intent(WikiActivity.this, LinkResolverActivity.class); + Uri uri = Uri.parse(link); + intent.setData(uri); + startActivity(intent); + }); + } + + @Override + public void configureTheme(@NonNull MarkwonTheme.Builder builder) { + builder.linkColor(linkColor); + } + }) + .usePlugin(StrikethroughPlugin.create()) + .usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS)) + .usePlugin(TableEntryPlugin.create(this)) + .build(); + + markwonAdapter = MarkwonAdapter.builder(R.layout.adapter_default_entry, R.id.text) + .include(TableBlock.class, TableEntry.create(builder -> builder + .tableLayout(R.layout.adapter_table_block, R.id.table_layout) + .textLayoutIsRoot(R.layout.view_table_entry_cell))) + .build(); + LinearLayoutManager linearLayoutManager = new MarkwonLinearLayoutManager(this, new MarkwonLinearLayoutManager.HorizontalScrollViewScrolledListener() { + @Override + public void onScrolledLeft() { + if (mSlidrInterface != null) { + mSlidrInterface.lock(); + } + } + + @Override + public void onScrolledRight() { + if (mSlidrInterface != null) { + mSlidrInterface.unlock(); + } + } + }); + markdownRecyclerView.setLayoutManager(linearLayoutManager); + markdownRecyclerView.setAdapter(markwonAdapter); + + if (savedInstanceState != null) { + wikiMarkdown = savedInstanceState.getString(WIKI_MARKDOWN_STATE); + } + + if (wikiMarkdown == null) { + loadWiki(); + } else { + markwonAdapter.setMarkdown(markwon, wikiMarkdown); + markwonAdapter.notifyDataSetChanged(); + } + } + + private void loadWiki() { + retrofit.create(RedditAPI.class).getWiki(getIntent().getStringExtra(EXTRA_SUBREDDIT_NAME)).enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.isSuccessful()) { + try { + markwonAdapter.setMarkdown(markwon, new JSONObject(response.body()) + .getJSONObject(JSONUtils.DATA_KEY).getString(JSONUtils.CONTENT_MD_KEY)); + markwonAdapter.notifyDataSetChanged(); + } catch (JSONException e) { + e.printStackTrace(); + } + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + + } + }); + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + + return false; + } + + @Override + protected void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString(WIKI_MARKDOWN_STATE, wikiMarkdown); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + EventBus.getDefault().unregister(this); + } + + @Override + protected SharedPreferences getDefaultSharedPreferences() { + return mSharedPreferences; + } + + @Override + protected CustomThemeWrapper getCustomThemeWrapper() { + return mCustomThemeWrapper; + } + + @Override + protected void applyCustomTheme() { + coordinatorLayout.setBackgroundColor(mCustomThemeWrapper.getBackgroundColor()); + applyAppBarLayoutAndToolbarTheme(appBarLayout, toolbar); + } + + @Subscribe + public void onAccountSwitchEvent(SwitchAccountEvent event) { + if (!getClass().getName().equals(event.excludeActivityClassName)) { + finish(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/apis/RedditAPI.java b/app/src/main/java/ml/docilealligator/infinityforreddit/apis/RedditAPI.java index 420b90a2..b7dfba9e 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/apis/RedditAPI.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/apis/RedditAPI.java @@ -365,4 +365,7 @@ public interface RedditAPI { @GET("/api/trending_searches_v1.json?withAds=0&raw_json=1&gilding_detail=1") Call getTrendingSearches(@HeaderMap Map headers); + + @GET("/r/{subredditName}/wiki/index.json?raw_json=1") + Call getWiki(@Path("subredditName") String subredditName); } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/utils/JSONUtils.java b/app/src/main/java/ml/docilealligator/infinityforreddit/utils/JSONUtils.java index 8d68d9fb..b76f198e 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/utils/JSONUtils.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/utils/JSONUtils.java @@ -170,4 +170,5 @@ public class JSONUtils { public static final String QUERY_STRING_KEY = "query_string"; public static final String DISPLAY_STRING_KEY = "display_string"; public static final String RESULTS_KEY = "results"; + public static final String CONTENT_MD_KEY = "content_md"; } -- cgit v1.2.3