PreviewTextFragment.java 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. /*
  2. * ownCloud Android client application
  3. *
  4. * Copyright (C) 2016 ownCloud Inc.
  5. *
  6. * This program is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License version 2,
  8. * as published by the Free Software Foundation.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. */
  19. package com.owncloud.android.ui.preview;
  20. import android.app.Activity;
  21. import android.content.Intent;
  22. import android.graphics.Color;
  23. import android.graphics.PorterDuff;
  24. import android.graphics.PorterDuffColorFilter;
  25. import android.net.Uri;
  26. import android.os.Bundle;
  27. import android.os.Handler;
  28. import android.text.Html;
  29. import android.text.Spanned;
  30. import android.text.TextPaint;
  31. import android.text.method.LinkMovementMethod;
  32. import android.view.LayoutInflater;
  33. import android.view.View;
  34. import android.view.ViewGroup;
  35. import android.widget.ImageView;
  36. import android.widget.ProgressBar;
  37. import android.widget.RelativeLayout;
  38. import android.widget.TextView;
  39. import com.nextcloud.client.account.UserAccountManager;
  40. import com.nextcloud.client.device.DeviceInfo;
  41. import com.nextcloud.client.di.Injectable;
  42. import com.owncloud.android.R;
  43. import com.owncloud.android.datamodel.OCFile;
  44. import com.owncloud.android.lib.common.utils.Log_OC;
  45. import com.owncloud.android.ui.activity.FileDisplayActivity;
  46. import com.owncloud.android.ui.fragment.FileFragment;
  47. import com.owncloud.android.utils.DisplayUtils;
  48. import com.owncloud.android.utils.MimeTypeUtil;
  49. import com.owncloud.android.utils.StringUtils;
  50. import com.owncloud.android.utils.ThemeUtils;
  51. import javax.inject.Inject;
  52. import androidx.annotation.NonNull;
  53. import androidx.appcompat.widget.SearchView;
  54. import io.noties.markwon.AbstractMarkwonPlugin;
  55. import io.noties.markwon.Markwon;
  56. import io.noties.markwon.MarkwonConfiguration;
  57. import io.noties.markwon.core.MarkwonTheme;
  58. import io.noties.markwon.ext.strikethrough.StrikethroughPlugin;
  59. import io.noties.markwon.ext.tables.TablePlugin;
  60. import io.noties.markwon.ext.tasklist.TaskListDrawable;
  61. import io.noties.markwon.ext.tasklist.TaskListPlugin;
  62. import io.noties.markwon.html.HtmlPlugin;
  63. import io.noties.markwon.syntax.Prism4jTheme;
  64. import io.noties.markwon.syntax.Prism4jThemeDefault;
  65. import io.noties.markwon.syntax.SyntaxHighlightPlugin;
  66. import io.noties.prism4j.Prism4j;
  67. import io.noties.prism4j.annotations.PrismBundle;
  68. @PrismBundle(
  69. include = {
  70. "c", "clike", "clojure", "cpp", "csharp", "css", "dart", "git", "go", "groovy", "java", "javascript", "json",
  71. "kotlin", "latex", "makefile", "markdown", "markup", "python", "scala", "sql", "swift", "yaml"
  72. },
  73. grammarLocatorClassName = ".MarkwonGrammarLocator"
  74. )
  75. public abstract class PreviewTextFragment extends FileFragment implements SearchView.OnQueryTextListener, Injectable {
  76. private static final String TAG = PreviewTextFragment.class.getSimpleName();
  77. protected SearchView mSearchView;
  78. protected String mSearchQuery = "";
  79. protected boolean mSearchOpen;
  80. protected TextView mTextPreview;
  81. protected Handler mHandler;
  82. protected RelativeLayout mMultiView;
  83. protected String mOriginalText;
  84. private TextView mMultiListMessage;
  85. private TextView mMultiListHeadline;
  86. private ImageView mMultiListIcon;
  87. private ProgressBar mMultiListProgress;
  88. @Inject UserAccountManager accountManager;
  89. @Inject DeviceInfo deviceInfo;
  90. /**
  91. * {@inheritDoc}
  92. */
  93. @Override
  94. public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
  95. Bundle savedInstanceState) {
  96. super.onCreateView(inflater, container, savedInstanceState);
  97. Log_OC.e(TAG, "onCreateView");
  98. View ret = inflater.inflate(R.layout.text_file_preview, container, false);
  99. mTextPreview = ret.findViewById(R.id.text_preview);
  100. mMultiView = ret.findViewById(R.id.multi_view);
  101. setupMultiView(ret);
  102. setMultiListLoadingMessage();
  103. return ret;
  104. }
  105. private void setupMultiView(View view) {
  106. mMultiListMessage = view.findViewById(R.id.empty_list_view_text);
  107. mMultiListHeadline = view.findViewById(R.id.empty_list_view_headline);
  108. mMultiListIcon = view.findViewById(R.id.empty_list_icon);
  109. mMultiListProgress = view.findViewById(R.id.empty_list_progress);
  110. }
  111. private void setMultiListLoadingMessage() {
  112. if (mMultiView != null) {
  113. mMultiListHeadline.setText(R.string.file_list_loading);
  114. mMultiListMessage.setText("");
  115. mMultiListIcon.setVisibility(View.GONE);
  116. mMultiListProgress.setVisibility(View.VISIBLE);
  117. }
  118. }
  119. @Override
  120. public void onStart() {
  121. super.onStart();
  122. Log_OC.e(TAG, "onStart");
  123. loadAndShowTextPreview();
  124. }
  125. abstract void loadAndShowTextPreview();
  126. @Override
  127. public boolean onQueryTextSubmit(String query) {
  128. performSearch(query, 0);
  129. return true;
  130. }
  131. @Override
  132. public boolean onQueryTextChange(final String newText) {
  133. performSearch(newText, 500);
  134. return true;
  135. }
  136. private void performSearch(final String query, int delay) {
  137. mHandler.removeCallbacksAndMessages(null);
  138. if (mOriginalText != null) {
  139. if (getActivity() instanceof FileDisplayActivity) {
  140. FileDisplayActivity fileDisplayActivity = (FileDisplayActivity) getActivity();
  141. fileDisplayActivity.setSearchQuery(query);
  142. }
  143. mHandler.postDelayed(() -> {
  144. if (query != null && !query.isEmpty()) {
  145. if (getContext() != null && getContext().getResources() != null) {
  146. String coloredText = StringUtils.searchAndColor(mOriginalText, query,
  147. getContext().getResources().getColor(R.color.primary));
  148. mTextPreview.setText(Html.fromHtml(coloredText.replace("\n", "<br \\>")));
  149. }
  150. } else {
  151. setText(mTextPreview, mOriginalText, getFile(), getActivity());
  152. }
  153. }, delay);
  154. }
  155. if (delay == 0 && mSearchView != null) {
  156. mSearchView.clearFocus();
  157. }
  158. }
  159. protected static Spanned getRenderedMarkdownText(Activity activity, String markdown) {
  160. Prism4j prism4j = new Prism4j(new MarkwonGrammarLocator());
  161. Prism4jTheme prism4jTheme = Prism4jThemeDefault.create();
  162. TaskListDrawable drawable = new TaskListDrawable(Color.GRAY, Color.GRAY, Color.WHITE);
  163. drawable.setColorFilter(ThemeUtils.primaryColor(activity, true), PorterDuff.Mode.SRC_ATOP);
  164. final Markwon markwon = Markwon.builder(activity)
  165. .usePlugin(new AbstractMarkwonPlugin() {
  166. @Override
  167. public void configureTheme(@NonNull MarkwonTheme.Builder builder) {
  168. TextPaint textPaint = new TextPaint();
  169. textPaint.setColorFilter(new PorterDuffColorFilter(ThemeUtils.primaryColor(activity),
  170. PorterDuff.Mode.SRC_ATOP));
  171. builder.linkColor(ThemeUtils.primaryColor(activity, true));
  172. }
  173. @Override
  174. public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
  175. builder.linkResolver((view, link) -> {
  176. Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(link));
  177. DisplayUtils.startIntentIfAppAvailable(intent, activity, R.string.no_browser_available);
  178. });
  179. }
  180. })
  181. .usePlugin(TablePlugin.create(activity))
  182. .usePlugin(TaskListPlugin.create(drawable))
  183. .usePlugin(StrikethroughPlugin.create())
  184. .usePlugin(HtmlPlugin.create())
  185. .usePlugin(SyntaxHighlightPlugin.create(prism4j, prism4jTheme))
  186. .build();
  187. return markwon.toMarkdown(markdown);
  188. }
  189. /**
  190. * Finishes the preview
  191. */
  192. protected void finish() {
  193. getActivity().runOnUiThread(() -> getActivity().onBackPressed());
  194. }
  195. public static void setText(TextView textView, String text, OCFile file, Activity activity) {
  196. setText(textView, text, file, activity, false);
  197. }
  198. public static void setText(TextView textView, String text, OCFile file, Activity activity, boolean ignoreMimetype) {
  199. if ((MimeTypeUtil.MIMETYPE_TEXT_MARKDOWN.equals(file.getMimeType()) || ignoreMimetype) &&
  200. android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN
  201. && activity != null) {
  202. textView.setMovementMethod(LinkMovementMethod.getInstance());
  203. textView.setText(getRenderedMarkdownText(activity, text));
  204. } else {
  205. textView.setText(text);
  206. }
  207. }
  208. }