6666import android .view .animation .AnimationUtils ;
6767import android .view .inputmethod .EditorInfo ;
6868import android .view .inputmethod .InputMethodManager ;
69+ import android .webkit .WebView ;
6970import android .widget .ScrollView ;
7071import android .widget .Toast ;
7172
@@ -86,12 +87,14 @@ public class TextEditorActivity extends ThemedActivity
8687 private Typeface inputTypefaceMono ;
8788 private androidx .appcompat .widget .Toolbar toolbar ;
8889 ScrollView scrollView ;
90+ private WebView markdownWebView ;
8991
9092 private SearchTextTask searchTextTask ;
9193 private static final String KEY_MODIFIED_TEXT = "modified" ;
9294 private static final String KEY_INDEX = "index" ;
9395 private static final String KEY_ORIGINAL_TEXT = "original" ;
9496 private static final String KEY_MONOFONT = "monofont" ;
97+ private static final String KEY_MARKDOWN_PREVIEW = "markdown_preview" ;
9598
9699 private ConstraintLayout searchViewLayout ;
97100 public AppCompatImageButton upButton ;
@@ -134,6 +137,8 @@ public void onCreate(Bundle savedInstanceState) {
134137 }
135138 mainTextView = findViewById (R .id .textEditorMainEditText );
136139 scrollView = findViewById (R .id .textEditorScrollView );
140+ markdownWebView = findViewById (R .id .textEditorMarkdownWebView );
141+ markdownWebView .getSettings ().setJavaScriptEnabled (false );
137142
138143 final Uri uri = getIntent ().getData ();
139144 if (uri != null ) {
@@ -178,6 +183,11 @@ public void onCreate(Bundle savedInstanceState) {
178183 if (savedInstanceState .getBoolean (KEY_MONOFONT )) {
179184 mainTextView .setTypeface (inputTypefaceMono );
180185 }
186+ // Restore markdown preview state
187+ if (savedInstanceState .getBoolean (KEY_MARKDOWN_PREVIEW , false )) {
188+ viewModel .setMarkdownPreviewEnabled (true );
189+ toggleMarkdownPreview (true );
190+ }
181191 // Restore windowed mode state after rotation
182192 if (viewModel .isWindowed ()) {
183193 setReadOnly ();
@@ -203,6 +213,7 @@ protected void onSaveInstanceState(@NonNull Bundle outState) {
203213 outState .putInt (KEY_INDEX , mainTextView .getScrollY ());
204214 outState .putString (KEY_ORIGINAL_TEXT , viewModel .getOriginal ());
205215 outState .putBoolean (KEY_MONOFONT , inputTypefaceMono .equals (mainTextView .getTypeface ()));
216+ outState .putBoolean (KEY_MARKDOWN_PREVIEW , viewModel .getMarkdownPreviewEnabled ());
206217 }
207218
208219 private void checkUnsavedChanges () {
@@ -309,6 +320,11 @@ public boolean onPrepareOptionsMenu(Menu menu) {
309320 // Hide search in windowed mode (search only works on in-memory text)
310321 menu .findItem (R .id .find ).setVisible (!windowed );
311322
323+ // Show markdown preview item only for .md/.markdown files
324+ MenuItem markdownItem = menu .findItem (R .id .markdown_preview );
325+ markdownItem .setVisible (isMarkdownFile ());
326+ markdownItem .setChecked (viewModel .getMarkdownPreviewEnabled ());
327+
312328 menu .findItem (R .id .monofont ).setChecked (inputTypefaceMono .equals (mainTextView .getTypeface ()));
313329 return super .onPrepareOptionsMenu (menu );
314330 }
@@ -362,6 +378,11 @@ public boolean onOptionsItemSelected(MenuItem item) {
362378 } else if (item .getItemId () == R .id .monofont ) {
363379 item .setChecked (!item .isChecked ());
364380 mainTextView .setTypeface (item .isChecked () ? inputTypefaceMono : inputTypefaceDefault );
381+ } else if (item .getItemId () == R .id .markdown_preview ) {
382+ boolean newState = !item .isChecked ();
383+ item .setChecked (newState );
384+ viewModel .setMarkdownPreviewEnabled (newState );
385+ toggleMarkdownPreview (newState );
365386 } else {
366387 return false ;
367388 }
@@ -661,7 +682,6 @@ private void observeWindowContent() {
661682
662683 // Determine an anchor: find the text line near the middle of the current viewport
663684 int oldScrollY = scrollView .getScrollY ();
664- Layout oldLayout = mainTextView .getLayout ();
665685
666686 // Replace text (TextWatcher will fire but windowed-mode guard skips modification
667687 // tracking)
@@ -673,7 +693,6 @@ private void observeWindowContent() {
673693 Layout newLayout = mainTextView .getLayout ();
674694 if (newLayout == null ) return ;
675695
676- TextEditorActivityViewModel .Direction direction ;
677696 // Infer direction from old scroll position
678697 int viewportHeight = scrollView .getHeight ();
679698 if (oldScrollY > viewportHeight / 2 ) {
@@ -728,4 +747,46 @@ public void initWindowedScrollListener() {
728747
729748 scrollView .getViewTreeObserver ().addOnScrollChangedListener (windowedScrollListener );
730749 }
750+
751+ // ── Markdown Preview Helpers ────────────────────────────────────────
752+
753+ /** Returns true if the currently opened file has a Markdown extension (.md or .markdown). */
754+ private boolean isMarkdownFile () {
755+ EditableFileAbstraction file = viewModel .getFile ();
756+ if (file == null ) return false ;
757+ return MarkdownHtmlGenerator .isMarkdownFile (file .name );
758+ }
759+
760+ /**
761+ * Toggle between Markdown preview (WebView) and the normal EditText editor.
762+ *
763+ * @param enabled true to show the WebView with rendered Markdown; false to show EditText
764+ */
765+ private void toggleMarkdownPreview (boolean enabled ) {
766+ if (enabled ) {
767+ renderMarkdownToWebView ();
768+ scrollView .setVisibility (View .GONE );
769+ markdownWebView .setVisibility (View .VISIBLE );
770+ } else {
771+ markdownWebView .setVisibility (View .GONE );
772+ scrollView .setVisibility (View .VISIBLE );
773+ }
774+ invalidateOptionsMenu ();
775+ }
776+
777+ /**
778+ * Parse the current EditText content as Markdown using commonmark, render to HTML, and load it
779+ * into the WebView.
780+ */
781+ private void renderMarkdownToWebView () {
782+ String markdownSource = "" ;
783+ if (mainTextView .getText () != null ) {
784+ markdownSource = mainTextView .getText ().toString ();
785+ }
786+
787+ String bodyHtml = MarkdownHtmlGenerator .renderToHtml (markdownSource );
788+ boolean isDark = getAppTheme ().equals (AppTheme .DARK ) || getAppTheme ().equals (AppTheme .BLACK );
789+ String fullHtml = MarkdownHtmlGenerator .wrapWithBaseHtml (bodyHtml , isDark );
790+ markdownWebView .loadDataWithBaseURL (null , fullHtml , "text/html" , "UTF-8" , null );
791+ }
731792}
0 commit comments