/* * ownCloud Android client application * * Copyright (C) 2016 ownCloud Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, * as published by the Free Software Foundation. * * This program 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 this program. If not, see . * */ package com.owncloud.android.ui.preview; import android.app.Activity; import android.content.Intent; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.text.Html; import android.text.Spanned; import android.text.TextPaint; import android.text.method.LinkMovementMethod; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; import com.nextcloud.client.account.UserAccountManager; import com.nextcloud.client.di.Injectable; import com.owncloud.android.R; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.ui.activity.FileDisplayActivity; import com.owncloud.android.ui.fragment.FileFragment; import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.MimeTypeUtil; import com.owncloud.android.utils.StringUtils; import com.owncloud.android.utils.ThemeUtils; import javax.inject.Inject; import androidx.annotation.NonNull; import androidx.appcompat.widget.SearchView; 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.ext.tables.TablePlugin; import io.noties.markwon.ext.tasklist.TaskListDrawable; import io.noties.markwon.ext.tasklist.TaskListPlugin; import io.noties.markwon.html.HtmlPlugin; import io.noties.markwon.syntax.Prism4jTheme; import io.noties.markwon.syntax.Prism4jThemeDefault; import io.noties.markwon.syntax.SyntaxHighlightPlugin; import io.noties.prism4j.Prism4j; import io.noties.prism4j.annotations.PrismBundle; @PrismBundle( include = { "c", "clike", "clojure", "cpp", "csharp", "css", "dart", "git", "go", "groovy", "java", "javascript", "json", "kotlin", "latex", "makefile", "markdown", "markup", "python", "scala", "sql", "swift", "yaml" }, grammarLocatorClassName = ".MarkwonGrammarLocator" ) public abstract class PreviewTextFragment extends FileFragment implements SearchView.OnQueryTextListener, Injectable { private static final String TAG = PreviewTextFragment.class.getSimpleName(); protected SearchView mSearchView; protected String mSearchQuery = ""; protected boolean mSearchOpen; protected TextView mTextPreview; protected Handler mHandler; protected RelativeLayout mMultiView; protected String mOriginalText; private TextView mMultiListMessage; private TextView mMultiListHeadline; private ImageView mMultiListIcon; private ProgressBar mMultiListProgress; @Inject UserAccountManager accountManager; /** * {@inheritDoc} */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); Log_OC.e(TAG, "onCreateView"); View ret = inflater.inflate(R.layout.text_file_preview, container, false); mTextPreview = ret.findViewById(R.id.text_preview); mMultiView = ret.findViewById(R.id.multi_view); setupMultiView(ret); setMultiListLoadingMessage(); return ret; } private void setupMultiView(View view) { mMultiListMessage = view.findViewById(R.id.empty_list_view_text); mMultiListHeadline = view.findViewById(R.id.empty_list_view_headline); mMultiListIcon = view.findViewById(R.id.empty_list_icon); mMultiListProgress = view.findViewById(R.id.empty_list_progress); } private void setMultiListLoadingMessage() { if (mMultiView != null) { mMultiListHeadline.setText(R.string.file_list_loading); mMultiListMessage.setText(""); mMultiListIcon.setVisibility(View.GONE); mMultiListProgress.setVisibility(View.VISIBLE); } } @Override public void onStart() { super.onStart(); Log_OC.e(TAG, "onStart"); loadAndShowTextPreview(); } abstract void loadAndShowTextPreview(); @Override public boolean onQueryTextSubmit(String query) { performSearch(query, 0); return true; } @Override public boolean onQueryTextChange(final String newText) { performSearch(newText, 500); return true; } private void performSearch(final String query, int delay) { mHandler.removeCallbacksAndMessages(null); if (mOriginalText != null) { if (getActivity() instanceof FileDisplayActivity) { FileDisplayActivity fileDisplayActivity = (FileDisplayActivity) getActivity(); fileDisplayActivity.setSearchQuery(query); } mHandler.postDelayed(() -> { if (query != null && !query.isEmpty()) { if (getContext() != null && getContext().getResources() != null) { String coloredText = StringUtils.searchAndColor(mOriginalText, query, getContext().getResources().getColor(R.color.primary)); mTextPreview.setText(Html.fromHtml(coloredText.replace("\n", "
"))); } } else { setText(mTextPreview, mOriginalText, getFile(), requireActivity()); } }, delay); } if (delay == 0 && mSearchView != null) { mSearchView.clearFocus(); } } protected static Spanned getRenderedMarkdownText(Activity activity, String markdown) { Prism4j prism4j = new Prism4j(new MarkwonGrammarLocator()); Prism4jTheme prism4jTheme = Prism4jThemeDefault.create(); TaskListDrawable drawable = new TaskListDrawable(Color.GRAY, Color.GRAY, Color.WHITE); drawable.setColorFilter(ThemeUtils.primaryColor(activity, true), PorterDuff.Mode.SRC_ATOP); final Markwon markwon = Markwon.builder(activity) .usePlugin(new AbstractMarkwonPlugin() { @Override public void configureTheme(@NonNull MarkwonTheme.Builder builder) { TextPaint textPaint = new TextPaint(); textPaint.setColorFilter(new PorterDuffColorFilter(ThemeUtils.primaryColor(activity), PorterDuff.Mode.SRC_ATOP)); builder.linkColor(ThemeUtils.primaryColor(activity, true)); } @Override public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { builder.linkResolver((view, link) -> { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(link)); DisplayUtils.startIntentIfAppAvailable(intent, activity, R.string.no_browser_available); }); } }) .usePlugin(TablePlugin.create(activity)) .usePlugin(TaskListPlugin.create(drawable)) .usePlugin(StrikethroughPlugin.create()) .usePlugin(HtmlPlugin.create()) .usePlugin(SyntaxHighlightPlugin.create(prism4j, prism4jTheme)) .build(); return markwon.toMarkdown(markdown); } /** * Finishes the preview */ protected void finish() { getActivity().runOnUiThread(() -> getActivity().onBackPressed()); } public static void setText(TextView textView, String text, OCFile file, Activity activity) { setText(textView, text, file, activity, false); } public static void setText(TextView textView, String text, OCFile file, Activity activity, boolean ignoreMimetype) { if ((MimeTypeUtil.MIMETYPE_TEXT_MARKDOWN.equals(file.getMimeType()) || ignoreMimetype) && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN && activity != null) { textView.setMovementMethod(LinkMovementMethod.getInstance()); textView.setText(getRenderedMarkdownText(activity, text)); } else { textView.setText(text); } } }