diff --git a/Simplenote/build.gradle b/Simplenote/build.gradle index a48c58be2..a203414f7 100644 --- a/Simplenote/build.gradle +++ b/Simplenote/build.gradle @@ -138,7 +138,7 @@ dependencies { implementation 'com.google.android.gms:play-services-wearable:19.0.0' // Third Party implementation 'com.squareup.okhttp3:okhttp:5.1.0' - implementation 'com.commonsware.cwac:anddown:0.4.0' + implementation("org.jetbrains:markdown:0.7.3") implementation 'net.openid:appauth:0.11.1' // Dagger Hilt dependencies diff --git a/Simplenote/src/main/java/com/automattic/simplenote/NoteMarkdownFragment.java b/Simplenote/src/main/java/com/automattic/simplenote/NoteMarkdownFragment.java deleted file mode 100644 index 5421fd26b..000000000 --- a/Simplenote/src/main/java/com/automattic/simplenote/NoteMarkdownFragment.java +++ /dev/null @@ -1,412 +0,0 @@ -package com.automattic.simplenote; - -import static com.automattic.simplenote.Simplenote.SCROLL_POSITION_PREFERENCES; -import static com.automattic.simplenote.analytics.AnalyticsTracker.CATEGORY_NOTE; -import static com.automattic.simplenote.utils.SimplenoteLinkify.SIMPLENOTE_LINK_PREFIX; - -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Handler; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.webkit.WebResourceRequest; -import android.webkit.WebView; -import android.webkit.WebViewClient; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.view.MenuCompat; -import androidx.core.widget.NestedScrollView; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentActivity; - -import com.automattic.simplenote.analytics.AnalyticsTracker; -import com.automattic.simplenote.models.Note; -import com.automattic.simplenote.utils.AppLog; -import com.automattic.simplenote.utils.AppLog.Type; -import com.automattic.simplenote.utils.BrowserUtils; -import com.automattic.simplenote.utils.ContextUtils; -import com.automattic.simplenote.utils.DrawableUtils; -import com.automattic.simplenote.utils.NetworkUtils; -import com.automattic.simplenote.utils.NoteUtils; -import com.automattic.simplenote.utils.SimplenoteLinkify; -import com.automattic.simplenote.utils.ThemeUtils; -import com.commonsware.cwac.anddown.AndDown; -import com.google.android.material.snackbar.Snackbar; -import com.simperium.client.Bucket; -import com.simperium.client.BucketObjectMissingException; - -import java.lang.ref.SoftReference; -import java.util.Set; - -public class NoteMarkdownFragment extends Fragment implements Bucket.Listener { - public static final String ARG_ITEM_ID = "item_id"; - - private Bucket mNotesBucket; - private Note mNote; - private SharedPreferences mPreferences; - private String mCss; - private WebView mMarkdown; - private boolean mIsLoadingNote; - - @Override - public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { - inflater.inflate(R.menu.note_markdown, menu); - MenuCompat.setGroupDividerEnabled(menu, true); - - DrawableUtils.tintMenuWithAttribute( - requireContext(), - menu, - R.attr.toolbarIconColor - ); - - for (int i = 0; i < menu.size(); i++) { - MenuItem item = menu.getItem(i); - DrawableUtils.setMenuItemAlpha(item, 0.3); // 0.3 is 30% opacity. - } - - if (mNote != null) { - MenuItem viewPublishedNoteItem = menu.findItem(R.id.menu_info); - viewPublishedNoteItem.setVisible(true); - MenuItem trashItem = menu.findItem(R.id.menu_trash); - - if (mNote.isDeleted()) { - trashItem.setTitle(R.string.restore); - } else { - trashItem.setTitle(R.string.trash); - } - - DrawableUtils.tintMenuItemWithAttribute(getActivity(), trashItem, R.attr.toolbarIconColor); - } - - super.onCreateOptionsMenu(menu, inflater); - } - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - AppLog.add(Type.SCREEN, "Created (NoteMarkdownFragment)"); - mNotesBucket = ((Simplenote) requireActivity().getApplication()).getNotesBucket(); - mPreferences = requireContext().getSharedPreferences(SCROLL_POSITION_PREFERENCES, Context.MODE_PRIVATE); - - // Load note if we were passed an ID. - Bundle arguments = getArguments(); - - if (arguments != null && arguments.containsKey(ARG_ITEM_ID)) { - String key = arguments.getString(ARG_ITEM_ID); - new LoadNoteTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, key); - } - - setHasOptionsMenu(true); - final View layout; - - if (BrowserUtils.isWebViewInstalled(requireContext())) { - layout = inflater.inflate(R.layout.fragment_note_markdown, container, false); - ((NestedScrollView) layout).setOnScrollChangeListener( - new NestedScrollView.OnScrollChangeListener() { - @Override - public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) { - mPreferences.edit().putInt(mNote.getSimperiumKey(), scrollY).apply(); - } - } - ); - mMarkdown = layout.findViewById(R.id.markdown); - - final long delay = requireContext().getResources().getInteger(android.R.integer.config_mediumAnimTime); - mMarkdown.setWebViewClient( - new WebViewClient() { - @Override - public void onPageFinished(final WebView view, String url) { - super.onPageFinished(view, url); - - new Handler().postDelayed( - new Runnable() { - @Override - public void run() { - if (mNote != null && mNote.getSimperiumKey() != null) { - ((NestedScrollView) layout).smoothScrollTo(0, mPreferences.getInt(mNote.getSimperiumKey(), 0)); - } - } - }, - delay - ); - } - - @Override - public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request){ - String url = request.getUrl().toString(); - - if (url.startsWith(SimplenoteLinkify.SIMPLENOTE_LINK_PREFIX)){ - AnalyticsTracker.track( - AnalyticsTracker.Stat.INTERNOTE_LINK_TAPPED, - AnalyticsTracker.CATEGORY_LINK, - "internote_link_tapped_markdown" - ); - SimplenoteLinkify.openNote(requireActivity(), url.replace(SIMPLENOTE_LINK_PREFIX, "")); - } else { - BrowserUtils.launchBrowserOrShowError(requireContext(), url); - } - - return true; - } - } - ); - mCss = ContextUtils.readCssFile(requireContext(), ThemeUtils.getCssFromStyle(requireContext())); - } else { - layout = inflater.inflate(R.layout.fragment_note_error, container, false); - layout.findViewById(R.id.error).setVisibility(View.VISIBLE); - layout.findViewById(R.id.button).setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - BrowserUtils.launchBrowserOrShowError(requireContext(), BrowserUtils.URL_WEB_VIEW); - } - } - ); - } - - return layout; - } - - @Override - public boolean onOptionsItemSelected(@NonNull MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - if (!isAdded()) { - return false; - } - - requireActivity().finish(); - return true; - case R.id.menu_delete: - NoteUtils.showDialogDeletePermanently(requireActivity(), mNote); - return true; - case R.id.menu_collaborators: - navigateToCollaborators(); - return true; - case R.id.menu_trash: - if (!isAdded()) { - return false; - } - - deleteNote(); - return true; - case R.id.menu_copy_internal: - AnalyticsTracker.track( - AnalyticsTracker.Stat.INTERNOTE_LINK_COPIED, - AnalyticsTracker.CATEGORY_LINK, - "internote_link_copied_markdown" - ); - - if (!isAdded()) { - return false; - } - - if (BrowserUtils.copyToClipboard(requireContext(), SimplenoteLinkify.getNoteLinkWithTitle(mNote.getTitle(), mNote.getSimperiumKey()))) { - Snackbar.make(mMarkdown, R.string.link_copied, Snackbar.LENGTH_SHORT).show(); - } else { - Snackbar.make(mMarkdown, R.string.link_copied_failure, Snackbar.LENGTH_SHORT).show(); - } - - return true; - default: - return super.onOptionsItemSelected(item); - } - } - - private void navigateToCollaborators() { - if (getActivity() == null || mNote == null) { - return; - } - - Intent intent = new Intent(requireActivity(), CollaboratorsActivity.class); - intent.putExtra(CollaboratorsActivity.NOTE_ID_ARG, mNote.getSimperiumKey()); - startActivity(intent); - - AnalyticsTracker.track( - AnalyticsTracker.Stat.EDITOR_COLLABORATORS_ACCESSED, - CATEGORY_NOTE, - "collaborators_ui_accessed" - ); - } - - private void deleteNote() { - NoteUtils.deleteNote(mNote, getActivity()); - requireActivity().finish(); - } - - @Override - public void onPrepareOptionsMenu(@NonNull Menu menu) { - // Show delete action only when note is in Trash. - menu.findItem(R.id.menu_delete).setVisible(mNote != null && mNote.isDeleted()); - // Disable trash action until note is loaded. - menu.findItem(R.id.menu_trash).setEnabled(!mIsLoadingNote); - - MenuItem pinItem = menu.findItem(R.id.menu_pin); - MenuItem publishItem = menu.findItem(R.id.menu_publish); - MenuItem copyLinkItem = menu.findItem(R.id.menu_copy); - MenuItem markdownItem = menu.findItem(R.id.menu_markdown); - MenuItem copyLinkInternalItem = menu.findItem(R.id.menu_copy_internal); - - if (mNote != null) { - pinItem.setChecked(mNote.isPinned()); - publishItem.setChecked(mNote.isPublished()); - markdownItem.setChecked(mNote.isMarkdownEnabled()); - } - - pinItem.setEnabled(false); - publishItem.setEnabled(false); - copyLinkItem.setEnabled(false); - markdownItem.setEnabled(false); - copyLinkInternalItem.setEnabled(true); - - super.onPrepareOptionsMenu(menu); - } - - @Override - public void onDestroy() { - super.onDestroy(); - mNotesBucket.removeListener(this); - AppLog.add(Type.SYNC, "Removed note bucket listener (NoteMarkdownFragment)"); - AppLog.add(Type.SCREEN, "Destroyed (NoteMarkdownFragment)"); - } - - @Override - public void onResume() { - super.onResume(); - // First inflation of the webview may invalidate the value of uiMode, - // so we re-apply it to make sure that the webview has the right css files - // Check https://issuetracker.google.com/issues/37124582 for more details - ((AppCompatActivity)requireActivity()).getDelegate().applyDayNight(); - checkWebView(); - mNotesBucket.addListener(this); - AppLog.add(Type.SYNC, "Added note bucket listener (NoteMarkdownFragment)"); - AppLog.add(Type.NETWORK, NetworkUtils.getNetworkInfo(requireContext())); - AppLog.add(Type.SCREEN, "Resumed (NoteMarkdownFragment)"); - } - - @Override - public void onBeforeUpdateObject(Bucket bucket, Note note) { - } - - @Override - public void onDeleteObject(Bucket bucket, Note note) { - } - - @Override - public void onNetworkChange(Bucket bucket, Bucket.ChangeType type, String key) { - } - - @Override - public void onSaveObject(Bucket bucket, Note note) { - if (note.equals(mNote)) { - mNote = note; - requireActivity().invalidateOptionsMenu(); - } - - AppLog.add( - Type.SYNC, - "Saved note callback in NoteMarkdownFragment (ID: " + note.getSimperiumKey() + - " / Title: " + note.getTitle() + - " / Characters: " + NoteUtils.getCharactersCount(note.getContent()) + - " / Words: " + NoteUtils.getWordCount(note.getContent()) + ")" - ); - } - - private void checkWebView() { - // When a WebView is installed and mMarkdown is null, a WebView was not installed when the - // fragment was created. So, open the note again to show the markdown preview. - if (BrowserUtils.isWebViewInstalled(requireContext()) && mMarkdown == null) { - SimplenoteLinkify.openNote(requireActivity(), mNote.getSimperiumKey()); - } - } - - public void updateMarkdown(String text) { - if (mMarkdown != null) { - mMarkdown.loadDataWithBaseURL(null, getMarkdownFormattedContent(mCss, text), "text/html", "utf-8", null); - } - } - - public static String getMarkdownFormattedContent(String cssContent, String sourceContent) { - String header = "" + - "" + - "\n" + - cssContent + ""; - - String parsedMarkdown = new AndDown().markdownToHtml( - sourceContent, - AndDown.HOEDOWN_EXT_STRIKETHROUGH | AndDown.HOEDOWN_EXT_FENCED_CODE | - AndDown.HOEDOWN_EXT_QUOTE | AndDown.HOEDOWN_EXT_TABLES, - AndDown.HOEDOWN_HTML_ESCAPE - ); - - // Set auto alignment for lists, tables, and quotes based on language of start. - parsedMarkdown = parsedMarkdown - .replaceAll("
    ", "
      ") - .replaceAll("
        ", "
          ") - .replaceAll("", "
          ") - .replaceAll("
          ", "
          "); - - return header + "
          " + parsedMarkdown + - "
          "; - } - - @Override - public void onLocalQueueChange(Bucket bucket, Set queuedObjects) { - - } - - @Override - public void onSyncObject(Bucket bucket, String key) { - - } - - private static class LoadNoteTask extends AsyncTask { - private SoftReference mNoteMarkdownFragmentReference; - - private LoadNoteTask(NoteMarkdownFragment context) { - mNoteMarkdownFragmentReference = new SoftReference<>(context); - } - - @Override - protected void onPreExecute() { - NoteMarkdownFragment fragment = mNoteMarkdownFragmentReference.get(); - fragment.mIsLoadingNote = true; - } - - @Override - protected Void doInBackground(String... args) { - NoteMarkdownFragment fragment = mNoteMarkdownFragmentReference.get(); - FragmentActivity activity = fragment.getActivity(); - - if (activity == null) { - return null; - } - - String noteID = args[0]; - Simplenote application = (Simplenote) activity.getApplication(); - Bucket notesBucket = application.getNotesBucket(); - - try { - fragment.mNote = notesBucket.get(noteID); - } catch (BucketObjectMissingException exception) { - // TODO: Handle a missing note - } - - return null; - } - - @Override - protected void onPostExecute(Void nada) { - NoteMarkdownFragment fragment = mNoteMarkdownFragmentReference.get(); - fragment.mIsLoadingNote = false; - fragment.requireActivity().invalidateOptionsMenu(); - } - } -} diff --git a/Simplenote/src/main/java/com/automattic/simplenote/NoteMarkdownFragment.kt b/Simplenote/src/main/java/com/automattic/simplenote/NoteMarkdownFragment.kt new file mode 100644 index 000000000..4aae20d29 --- /dev/null +++ b/Simplenote/src/main/java/com/automattic/simplenote/NoteMarkdownFragment.kt @@ -0,0 +1,380 @@ +package com.automattic.simplenote + +import android.content.Context +import android.content.Intent +import android.content.SharedPreferences +import android.os.AsyncTask +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import android.webkit.WebResourceRequest +import android.webkit.WebView +import android.webkit.WebViewClient +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.MenuCompat +import androidx.core.widget.NestedScrollView +import androidx.fragment.app.Fragment +import com.automattic.simplenote.Simplenote.SCROLL_POSITION_PREFERENCES +import com.automattic.simplenote.analytics.AnalyticsTracker +import com.automattic.simplenote.analytics.AnalyticsTracker.CATEGORY_NOTE +import com.automattic.simplenote.models.Note +import com.automattic.simplenote.utils.AppLog +import com.automattic.simplenote.utils.AppLog.Type +import com.automattic.simplenote.utils.BrowserUtils +import com.automattic.simplenote.utils.ContextUtils +import com.automattic.simplenote.utils.DrawableUtils +import com.automattic.simplenote.utils.NetworkUtils +import com.automattic.simplenote.utils.NoteUtils +import com.automattic.simplenote.utils.SimplenoteLinkify +import com.automattic.simplenote.utils.SimplenoteLinkify.SIMPLENOTE_LINK_PREFIX +import com.automattic.simplenote.utils.ThemeUtils +import com.automattic.simplenote.utils.markdown.SimplenoteMarkdownFlavorDescriptor +import com.google.android.material.snackbar.Snackbar +import com.simperium.client.Bucket +import com.simperium.client.BucketObjectMissingException +import org.intellij.markdown.html.HtmlGenerator +import org.intellij.markdown.parser.MarkdownParser +import java.lang.ref.SoftReference + +class NoteMarkdownFragment : Fragment(), Bucket.Listener { + + companion object { + const val ARG_ITEM_ID = "item_id" + + @JvmStatic + fun getMarkdownFormattedContent(cssContent: String, sourceContent: String): String { + val header = "" + + "" + + "\n" + + cssContent + "" + + val flavour = SimplenoteMarkdownFlavorDescriptor() + val parsedTree = MarkdownParser(flavour).buildMarkdownTreeFromString(sourceContent) + var parsedMarkdown = HtmlGenerator(sourceContent, parsedTree, flavour).generateHtml() + + // Set auto alignment for lists, tables, and quotes based on language of start. + parsedMarkdown = parsedMarkdown + .replace("
            ", "
              ") + .replace("
                ", "
                  ") + .replace("
          ", "
          ") + .replace("
          ", "
          ") + + return header + "
          " + parsedMarkdown + + "
          " + } + } + + private var mNotesBucket: Bucket? = null + private var mNote: Note? = null + private var mPreferences: SharedPreferences? = null + private var mCss: String? = null + private var mMarkdown: WebView? = null + private var mIsLoadingNote = false + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.note_markdown, menu) + MenuCompat.setGroupDividerEnabled(menu, true) + + DrawableUtils.tintMenuWithAttribute( + requireContext(), + menu, + R.attr.toolbarIconColor + ) + + for (i in 0 until menu.size()) { + val item = menu.getItem(i) + DrawableUtils.setMenuItemAlpha(item, 0.3) // 0.3 is 30% opacity. + } + + mNote?.let { note -> + val viewPublishedNoteItem = menu.findItem(R.id.menu_info) + viewPublishedNoteItem.isVisible = true + val trashItem = menu.findItem(R.id.menu_trash) + + if (note.isDeleted) { + trashItem.setTitle(R.string.restore) + } else { + trashItem.setTitle(R.string.trash) + } + + DrawableUtils.tintMenuItemWithAttribute(activity, trashItem, R.attr.toolbarIconColor) + } + + super.onCreateOptionsMenu(menu, inflater) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + AppLog.add(Type.SCREEN, "Created (NoteMarkdownFragment)") + mNotesBucket = (requireActivity().application as Simplenote).notesBucket + mPreferences = requireContext().getSharedPreferences(SCROLL_POSITION_PREFERENCES, Context.MODE_PRIVATE) + + // Load note if we were passed an ID. + val arguments = arguments + if (arguments != null && arguments.containsKey(ARG_ITEM_ID)) { + val key = arguments.getString(ARG_ITEM_ID) + LoadNoteTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, key) + } + + setHasOptionsMenu(true) + val layout: View + + if (BrowserUtils.isWebViewInstalled(requireContext())) { + layout = inflater.inflate(R.layout.fragment_note_markdown, container, false) + (layout as NestedScrollView).setOnScrollChangeListener { _, _, scrollY, _, _ -> + mNote?.let { note -> + mPreferences?.edit()?.putInt(note.simperiumKey, scrollY)?.apply() + } + } + mMarkdown = layout.findViewById(R.id.markdown) + + val delay = requireContext().resources.getInteger(android.R.integer.config_mediumAnimTime).toLong() + mMarkdown?.webViewClient = object : WebViewClient() { + override fun onPageFinished(view: WebView, url: String) { + super.onPageFinished(view, url) + + Handler(Looper.getMainLooper()).postDelayed({ + mNote?.let { note -> + note.simperiumKey?.let { key -> + (layout as NestedScrollView).smoothScrollTo(0, mPreferences?.getInt(key, 0) ?: 0) + } + } + }, delay) + } + + override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { + val url = request.url.toString() + + if (url.startsWith(SimplenoteLinkify.SIMPLENOTE_LINK_PREFIX)) { + AnalyticsTracker.track( + AnalyticsTracker.Stat.INTERNOTE_LINK_TAPPED, + AnalyticsTracker.CATEGORY_LINK, + "internote_link_tapped_markdown" + ) + SimplenoteLinkify.openNote(requireActivity(), url.replace(SIMPLENOTE_LINK_PREFIX, "")) + } else { + BrowserUtils.launchBrowserOrShowError(requireContext(), url) + } + + return true + } + } + mCss = ContextUtils.readCssFile(requireContext(), ThemeUtils.getCssFromStyle(requireContext())) + } else { + layout = inflater.inflate(R.layout.fragment_note_error, container, false) + layout.findViewById(R.id.error).visibility = View.VISIBLE + layout.findViewById(R.id.button).setOnClickListener { + BrowserUtils.launchBrowserOrShowError(requireContext(), BrowserUtils.URL_WEB_VIEW) + } + } + + return layout + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + android.R.id.home -> { + if (!isAdded) { + return false + } + requireActivity().finish() + true + } + R.id.menu_delete -> { + mNote?.let { note -> + NoteUtils.showDialogDeletePermanently(requireActivity(), note) + } + true + } + R.id.menu_collaborators -> { + navigateToCollaborators() + true + } + R.id.menu_trash -> { + if (!isAdded) { + return false + } + deleteNote() + true + } + R.id.menu_copy_internal -> { + AnalyticsTracker.track( + AnalyticsTracker.Stat.INTERNOTE_LINK_COPIED, + AnalyticsTracker.CATEGORY_LINK, + "internote_link_copied_markdown" + ) + + if (!isAdded) { + return false + } + + mNote?.let { note -> + if (BrowserUtils.copyToClipboard(requireContext(), SimplenoteLinkify.getNoteLinkWithTitle(note.title, note.simperiumKey))) { + mMarkdown?.let { markdown -> + Snackbar.make(markdown, R.string.link_copied, Snackbar.LENGTH_SHORT).show() + } + } else { + mMarkdown?.let { markdown -> + Snackbar.make(markdown, R.string.link_copied_failure, Snackbar.LENGTH_SHORT).show() + } + } + } + true + } + else -> super.onOptionsItemSelected(item) + } + } + + private fun navigateToCollaborators() { + if (activity == null || mNote == null) { + return + } + + val intent = Intent(requireActivity(), CollaboratorsActivity::class.java) + intent.putExtra(CollaboratorsActivity.NOTE_ID_ARG, mNote?.simperiumKey) + startActivity(intent) + + AnalyticsTracker.track( + AnalyticsTracker.Stat.EDITOR_COLLABORATORS_ACCESSED, + CATEGORY_NOTE, + "collaborators_ui_accessed" + ) + } + + private fun deleteNote() { + NoteUtils.deleteNote(mNote, activity) + requireActivity().finish() + } + + override fun onPrepareOptionsMenu(menu: Menu) { + // Show delete action only when note is in Trash. + menu.findItem(R.id.menu_delete).isVisible = mNote != null && mNote!!.isDeleted + // Disable trash action until note is loaded. + menu.findItem(R.id.menu_trash).isEnabled = !mIsLoadingNote + + val pinItem = menu.findItem(R.id.menu_pin) + val publishItem = menu.findItem(R.id.menu_publish) + val copyLinkItem = menu.findItem(R.id.menu_copy) + val markdownItem = menu.findItem(R.id.menu_markdown) + val copyLinkInternalItem = menu.findItem(R.id.menu_copy_internal) + + mNote?.let { note -> + pinItem.isChecked = note.isPinned + publishItem.isChecked = note.isPublished + markdownItem.isChecked = note.isMarkdownEnabled + } + + pinItem.isEnabled = false + publishItem.isEnabled = false + copyLinkItem.isEnabled = false + markdownItem.isEnabled = false + copyLinkInternalItem.isEnabled = true + + super.onPrepareOptionsMenu(menu) + } + + override fun onDestroy() { + super.onDestroy() + mNotesBucket?.removeListener(this) + AppLog.add(Type.SYNC, "Removed note bucket listener (NoteMarkdownFragment)") + AppLog.add(Type.SCREEN, "Destroyed (NoteMarkdownFragment)") + } + + override fun onResume() { + super.onResume() + // First inflation of the webview may invalidate the value of uiMode, + // so we re-apply it to make sure that the webview has the right css files + // Check https://issuetracker.google.com/issues/37124582 for more details + (requireActivity() as AppCompatActivity).delegate.applyDayNight() + checkWebView() + mNotesBucket?.addListener(this) + AppLog.add(Type.SYNC, "Added note bucket listener (NoteMarkdownFragment)") + AppLog.add(Type.NETWORK, NetworkUtils.getNetworkInfo(requireContext())) + AppLog.add(Type.SCREEN, "Resumed (NoteMarkdownFragment)") + } + + override fun onBeforeUpdateObject(bucket: Bucket, note: Note) { + } + + override fun onDeleteObject(bucket: Bucket, note: Note) { + } + + override fun onNetworkChange(bucket: Bucket, type: Bucket.ChangeType, key: String) { + } + + override fun onSaveObject(bucket: Bucket, note: Note) { + if (note.equals(mNote)) { + mNote = note + requireActivity().invalidateOptionsMenu() + } + + AppLog.add( + Type.SYNC, + "Saved note callback in NoteMarkdownFragment (ID: " + note.simperiumKey + + " / Title: " + note.title + + " / Characters: " + NoteUtils.getCharactersCount(note.content) + + " / Words: " + NoteUtils.getWordCount(note.content) + ")" + ) + } + + private fun checkWebView() { + // When a WebView is installed and mMarkdown is null, a WebView was not installed when the + // fragment was created. So, open the note again to show the markdown preview. + if (BrowserUtils.isWebViewInstalled(requireContext()) && mMarkdown == null) { + mNote?.let { note -> + SimplenoteLinkify.openNote(requireActivity(), note.simperiumKey) + } + } + } + + fun updateMarkdown(text: String) { + mMarkdown?.let { markdown -> + mCss?.let { css -> + markdown.loadDataWithBaseURL(null, getMarkdownFormattedContent(css, text), "text/html", "utf-8", null) + } + } + } + + override fun onLocalQueueChange(bucket: Bucket, queuedObjects: Set) { + } + + override fun onSyncObject(bucket: Bucket, key: String) { + } + + private class LoadNoteTask(context: NoteMarkdownFragment) : AsyncTask() { + private val mNoteMarkdownFragmentReference: SoftReference = SoftReference(context) + + override fun onPreExecute() { + val fragment = mNoteMarkdownFragmentReference.get() + fragment?.mIsLoadingNote = true + } + + override fun doInBackground(vararg args: String): Void? { + val fragment = mNoteMarkdownFragmentReference.get() ?: return null + val activity = fragment.activity ?: return null + + val noteID = args[0] + val application = activity.application as Simplenote + val notesBucket = application.notesBucket + + try { + fragment.mNote = notesBucket.get(noteID) + } catch (exception: BucketObjectMissingException) { + // TODO: Handle a missing note + } + + return null + } + + override fun onPostExecute(nada: Void?) { + val fragment = mNoteMarkdownFragmentReference.get() + fragment?.mIsLoadingNote = false + fragment?.requireActivity()?.invalidateOptionsMenu() + } + } +} diff --git a/Simplenote/src/main/java/com/automattic/simplenote/utils/markdown/SimplenoteMarkdownFlavorDescriptor.kt b/Simplenote/src/main/java/com/automattic/simplenote/utils/markdown/SimplenoteMarkdownFlavorDescriptor.kt new file mode 100644 index 000000000..14c3ed477 --- /dev/null +++ b/Simplenote/src/main/java/com/automattic/simplenote/utils/markdown/SimplenoteMarkdownFlavorDescriptor.kt @@ -0,0 +1,19 @@ +package com.automattic.simplenote.utils.markdown + +import org.intellij.markdown.IElementType +import org.intellij.markdown.flavours.gfm.GFMElementTypes +import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor +import org.intellij.markdown.html.GeneratingProvider +import org.intellij.markdown.html.SimpleInlineTagProvider +import org.intellij.markdown.html.URI +import org.intellij.markdown.parser.LinkMap + +class SimplenoteMarkdownFlavorDescriptor : GFMFlavourDescriptor() { + override fun createHtmlGeneratingProviders(linkMap: LinkMap, + baseURI: URI?): Map { + return super.createHtmlGeneratingProviders(linkMap, baseURI) + hashMapOf( + GFMElementTypes.STRIKETHROUGH to object : SimpleInlineTagProvider("del", 2, -2) { + }, + ) + } +}