Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 88 additions & 25 deletions app/src/main/java/com/git/amarradi/leafpad/NoteEditActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,29 @@
import com.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.textfield.TextInputLayout;

import android.text.method.LinkMovementMethod;
import android.widget.TextView;

import java.util.List;
import java.util.Objects;

public class NoteEditActivity extends AppCompatActivity {

private EditText titleEdit;
private EditText bodyEdit;
private TextView previewBody;
private Note note;
private NoteViewModel noteViewModel;
private MaterialToolbar toolbar;
private Resources res;
private boolean shouldPersistOnPause = true;
private boolean isNoteDeleted = false;
private NestedScrollView bodyScroll;

private boolean isNewNote = false;
private boolean fromSearch = false;
private boolean isUIConfigured = false;

private MenuItem saveMenuItem;

@SuppressLint("MissingInflatedId")
Expand Down Expand Up @@ -88,7 +95,7 @@ protected void onCreate(Bundle savedInstanceState) {
}
handleIntent(getIntent());
fromSearch = getIntent().getBooleanExtra("fromSearch", false);
observeNote();
observeViewModel();

View rootEdit = findViewById(R.id.all);
View toolbar = findViewById(R.id.toolbar);
Expand Down Expand Up @@ -170,35 +177,50 @@ private void configureUIFromNote(Note note) {
}

private void initViews() {
TextInputLayout titleLayout = findViewById(R.id.default_text_input_layout);
TextInputLayout bodyLayout = findViewById(R.id.body_text_input_layout);
titleEdit = findViewById(R.id.title_edit);
bodyEdit = findViewById(R.id.body_edit);

previewBody = findViewById(R.id.preview_body); // Initialize TextView
bodyScroll = findViewById(R.id.body_scroll);

bodyEdit.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
// clickable links on preview
previewBody.setMovementMethod(LinkMovementMethod.getInstance());

// one TextWatcher to update ViewModel on real time
TextWatcher textWatcher = new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) { }
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}

@Override
public void afterTextChanged(Editable s) {
bodyEdit.post(() -> scrollToCursor());
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (noteViewModel.getSelectedNote().getValue() != null) {
if (getCurrentFocus() == titleEdit) {
noteViewModel.updateNoteTitle(s.toString());
} else if (getCurrentFocus() == bodyEdit) {
noteViewModel.updateNoteBody(s.toString());
}
}
}
});

bodyEdit.setOnFocusChangeListener((v, hasFocus) -> {
if (hasFocus) {
bodyEdit.postDelayed(this::scrollToCursor, 250);
@Override
public void afterTextChanged(Editable s) {
if (getCurrentFocus() == bodyEdit) {
// Stelle sicher, dass Cursor immer sichtbar ist
bodyEdit.post(() -> {
int selection = bodyEdit.getSelectionStart();
Layout layout = bodyEdit.getLayout();
if (layout != null && selection > 0) {
int line = layout.getLineForOffset(selection);
int y = layout.getLineBottom(line);
bodyScroll.smoothScrollTo(0, y);
}
});
}
}
});

bodyEdit.setOnClickListener(v -> {
bodyEdit.postDelayed(this::scrollToCursor, 250);
});
};

titleLayout.setHintEnabled(false);
bodyLayout.setHintEnabled(false);
titleEdit.addTextChangedListener(textWatcher);
bodyEdit.addTextChangedListener(textWatcher);
}

private void scrollToCursor() {
Expand All @@ -216,15 +238,50 @@ private boolean isNewEntry(Note note) {
note.getBody() == null || note.getBody().isEmpty());
}

private void observeViewModel() {
// Observer for selected note (initial launch)
noteViewModel.getSelectedNote().observe(this, currentNote -> {
if (currentNote != null && !isUIConfigured) {
this.note = currentNote;
titleEdit.setText(currentNote.getTitle());
bodyEdit.setText(currentNote.getBody());
isUIConfigured = true; // Prevents reload when rotating screen
invalidateOptionsMenu();
}
});

// observer for state of the preview
noteViewModel.isPreviewActive().observe(this, isActive -> {
bodyEdit.setVisibility(isActive ? View.GONE : View.VISIBLE);
previewBody.setVisibility(isActive ? View.VISIBLE : View.GONE);

// if preview is active, force refresh of Spanned
if (isActive) {
noteViewModel.getSelectedNote().setValue(noteViewModel.getSelectedNote().getValue());
}

invalidateOptionsMenu(); // refresh menu icon
});

// Observer for parsed body (updates TextView of the preview)
noteViewModel.parsedBodyAsSpanned.observe(this, spanned -> {
previewBody.setText(spanned);
});
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_note_edit, menu);
saveMenuItem = menu.findItem(R.id.action_save);
if (saveMenuItem != null) {
saveMenuItem.setEnabled(false); // Initial disabled

// Updates preview icon depending on state
MenuItem previewItem = menu.findItem(R.id.action_preview);
if (noteViewModel.isPreviewActive().getValue() != null && noteViewModel.isPreviewActive().getValue()) {
previewItem.setIcon(R.drawable.ic_edit); // Changes to icon "edit"
} else {
previewItem.setIcon(R.drawable.ic_preview); // icon "preview"
}
Note current = noteViewModel.getSelectedNote().getValue();
if (current != null) {

if (note != null) {
MenuItem recipeItem = menu.findItem(R.id.action_recipe);
boolean isRecipe = current.getCategory() != null &&
current.getCategory().equals(res.getStringArray(R.array.category)[0]);
Expand All @@ -242,6 +299,12 @@ public boolean onCreateOptionsMenu(Menu menu) {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();

if (id == R.id.action_preview) {
noteViewModel.togglePreview();
return true;
}

switch (id) {
case R.id.action_recipe: {
Note current = noteViewModel.getSelectedNote().getValue();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.git.amarradi.leafpad.helper;

public class MarkdownParser {

public static String parse(String rawText) {
StringBuilder result = new StringBuilder();
String[] lines = rawText.split("\n");

for (String line : lines) {
String parsedLine = line;

// Heading: "# Heading"
if (parsedLine.startsWith("# ")) {
parsedLine = "<h1>" + parsedLine.substring(2).trim() + "</h1>";
}

// Bullet point: "* bullet"
else if (parsedLine.startsWith("* ")) {
parsedLine = context.getString(R.string.bullet_point_symbol) + parsedLine.substring(2).trim();
}

// Underline: "__underlined__"
parsedLine = parsedLine.replaceAll("__(.*?)__", "<u>$1</u>");

// Link: [text](url)
parsedLine = parsedLine.replaceAll("\\[(.+?)\\]\\((http.*?)\\)", "<a href=\"$2\">$1</a>");

result.append(parsedLine).append("<br>");
}

return result.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import android.app.Application;
import android.content.Context;
import android.os.Build;
import android.text.Html;
import android.text.Spanned;
import android.util.Log;

import androidx.annotation.NonNull;
Expand All @@ -12,6 +15,7 @@
import androidx.lifecycle.Transformations;

import com.git.amarradi.leafpad.Leafpad;
import com.git.amarradi.leafpad.helper.MarkdownParser;
import com.git.amarradi.leafpad.helper.ReleaseNoteHelper;
import com.git.amarradi.leafpad.model.Leaf;
import com.git.amarradi.leafpad.model.Note;
Expand All @@ -21,7 +25,8 @@
import java.util.List;
import java.util.Objects;

public class NoteViewModel extends AndroidViewModel {

public class NoteViewModel extends AndroidViewModel {

private final MutableLiveData<List<Note>> notesLiveData = new MutableLiveData<>();
private static final MutableLiveData<Note> selectedNote = new MutableLiveData<>();
Expand All @@ -44,10 +49,30 @@ public LiveData<ReleaseNote> getReleaseNote() {
}
private final MediatorLiveData<List<Object>> combinedNotes = new MediatorLiveData<>();
public LiveData<List<Object>> getCombinedNotes() { return combinedNotes; }
private final MutableLiveData<Boolean> isPreviewActive = new MutableLiveData<>(false);

public final LiveData<Spanned> parsedBodyAsSpanned = Transformations.map(selectedNote, note -> {
if (note == null || note.getBody() == null) {
return Html.fromHtml("", Html.FROM_HTML_MODE_LEGACY);
}
String html = MarkdownParser.parse(note.getBody());
// Use Html.fromHtml to convert our HTML to an Spanned object that TextView can render
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY);
} else {
return Html.fromHtml(html);
}
});
private final MediatorLiveData<Boolean> isNoteModified = new MediatorLiveData<>();


public LiveData<Boolean> isPreviewActive() {
return isPreviewActive;
}

public void togglePreview() {
isPreviewActive.setValue(Boolean.FALSE.equals(isPreviewActive.getValue()));
}

public void checkAndLoadReleaseNote(Context context) {
int savedVersion = Leafpad.getCurrentLeafpadVersionCode(context); // default = 0
Expand Down Expand Up @@ -285,6 +310,19 @@ public void setNote(Note note) {
selectedNote.setValue(new Note(note));
}
}
public void updateNoteBody(String newBody) {
Note currentNote = selectedNote.getValue();
if (currentNote != null && !Objects.equals(currentNote.getBody(), newBody)) {
currentNote.setBody(newBody);
}
}

public void updateNoteTitle(String newTitle) {
Note currentNote = selectedNote.getValue();
if (currentNote != null && !Objects.equals(currentNote.getTitle(), newTitle)) {
currentNote.setTitle(newTitle);
}
}
public void loadNotes() {
Boolean showHidden = showHiddenLiveData.getValue();
if (showHidden == null) showHidden = false;
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/ic_edit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
</vector>
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/ic_preview.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5C21.27,7.61 17,4.5 12,4.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
</vector>
37 changes: 27 additions & 10 deletions app/src/main/res/layout/activity_note_edit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,33 @@
android:paddingStart="@dimen/switch_layout_margin_start"
android:paddingEnd="@dimen/switch_layout_margin_end">

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/body_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="top|start"
android:inputType="textMultiLine|textCapSentences|textAutoCorrect"
android:overScrollMode="always"
android:scrollbars="vertical"
android:background="@android:color/transparent"
android:textAlignment="viewStart" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="300dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/body_edit"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="top|start"
android:inputType="textMultiLine|textCapSentences|textAutoCorrect"
android:overScrollMode="always"
android:scrollbars="vertical"
android:background="@android:color/transparent"
android:textAlignment="viewStart"
android:visibility="visible" />
<TextView
android:id="@+id/preview_body"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:textSize="16sp"
android:textColor="?android:attr/textColorPrimary"
android:lineSpacingExtra="4dp"
android:textIsSelectable="true"
android:visibility="gone" />
</FrameLayout>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/res/menu/menu_note_edit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
android:icon="@drawable/ic_share"
android:title="@string/action_share_note"
app:showAsAction="ifRoom" />

<item
android:id="@+id/action_preview"
android:icon="@drawable/ic_preview"
android:title="@string/action_preview"
app:showAsAction="ifRoom" />

<item
android:id="@+id/action_save"
android:icon="@drawable/btn_save_note"
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
<string name="contentDescription">add new Note</string>
<string name="imported">note was send to leafpad</string>
<string name="edited_text">edited at</string>
<string name="action_preview">preview</string>
<string name="bullet_point_symbol">• </string>
<string name="new_note">new Note</string>
<string name="satisfied">Enjoying leafpad?</string>
<string name="rate_the_app">How about a review in Google\'s PlayStore?</string>
Expand Down