Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
c3cfbc4
feat(capabilities): Fetch and store direct editing capability using e…
AlvaroBrey Feb 15, 2023
d9aa4e7
feat: Implement direct editing repo
AlvaroBrey Feb 15, 2023
a133f9a
wip: Edit note with webview
AlvaroBrey Feb 17, 2023
4c1b5c8
wip: allow switching between the three note opening modes in preferences
AlvaroBrey Feb 24, 2023
8eea351
EditNoteActivity: if no setting, use plain edit, not direct edit
AlvaroBrey Feb 24, 2023
61a4399
feat: Add FAB to switch to rich editing mode from plain edit/preview
AlvaroBrey Feb 24, 2023
c0b2783
feat: add fab while direct editing to switch to normal editing
AlvaroBrey Feb 24, 2023
05a76e4
Fix toolbar when switching between direct edit and normal edit
AlvaroBrey Feb 27, 2023
3e4c90c
wip: error and conflict handling when switching edit modes
AlvaroBrey Mar 1, 2023
c3ea2f6
Only show direct editing FAB if direct editing is available
AlvaroBrey Mar 2, 2023
ff0a48c
EditNoteActivity: if pref is direct edit but it's not available, laun…
AlvaroBrey Mar 2, 2023
3fd864f
Show error if direct editing not loaded after 10 seconds
AlvaroBrey Mar 2, 2023
145af2e
Update user agent for Notes webview
AlvaroBrey Mar 2, 2023
6ac80ef
Support opening new notes with direct editing
AlvaroBrey Mar 2, 2023
08fad49
Allow invalid ssl certs for debug builds in webview
AlvaroBrey Mar 7, 2023
7b169a0
NoteDirectEdit: prevent duplicate note creation when creating it with…
AlvaroBrey Mar 7, 2023
5e517ff
Fix create with plain edit -> direct edit flow
AlvaroBrey Mar 7, 2023
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
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ dependencies {

// ReactiveX
implementation 'io.reactivex.rxjava2:rxjava:2.2.21'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'

// Testing
testImplementation 'androidx.test:core:1.5.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import android.app.Application;
import android.content.Context;
import android.util.Log;
import android.webkit.WebView;

import androidx.appcompat.app.AppCompatDelegate;
import androidx.preference.PreferenceManager;
Expand All @@ -29,6 +30,9 @@ public void onCreate() {
lockedPreference = prefs.getBoolean(getString(R.string.pref_key_lock), false);
isGridViewEnabled = getDefaultSharedPreferences(this).getBoolean(getString(R.string.pref_key_gridview), false);
super.onCreate();
if (BuildConfig.DEBUG) {
WebView.setWebContentsDebuggingEnabled(true);
}
}

public static void setAppTheme(DarkModeSetting setting) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego
private Note originalNote;
private int originalScrollY;
protected NotesRepository repo;
private NoteFragmentListener listener;
@Nullable
protected NoteFragmentListener listener;
private boolean titleModified = false;

protected boolean isNew = true;
Expand Down Expand Up @@ -143,6 +144,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat
@Nullable
protected abstract ScrollView getScrollView();


protected abstract void scrollToY(int scrollY);

@Override
Expand Down Expand Up @@ -240,7 +242,7 @@ public boolean onOptionsItemSelected(MenuItem item) {
.show(requireActivity().getSupportFragmentManager(), BaseNoteFragment.class.getSimpleName()));
return true;
} else if (itemId == R.id.menu_share) {
ShareUtil.openShareDialog(requireContext(), note.getTitle(), note.getContent());
shareNote();
return false;
} else if (itemId == MENU_ID_PIN) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Expand All @@ -263,6 +265,10 @@ public boolean onOptionsItemSelected(MenuItem item) {
return super.onOptionsItemSelected(item);
}

protected void shareNote() {
ShareUtil.openShareDialog(requireContext(), note.getTitle(), note.getContent());
}

@CallSuper
protected void onNoteLoaded(Note note) {
this.originalScrollY = note.getScrollY();
Expand All @@ -273,10 +279,21 @@ protected void onNoteLoaded(Note note) {
if (scrollY > 0) {
note.setScrollY(scrollY);
}
onScroll(scrollY, oldScrollY);
});
}
}

/**
* Scroll callback, to be overridden by subclasses. Default implementation is empty
*/
protected void onScroll(int scrollY, int oldScrollY) {
}

protected boolean shouldShowToolbar() {
return true;
}

public void onCloseNote() {
if (!titleModified && originalNote == null && getContent().isEmpty()) {
repo.deleteNoteAndSync(localAccount, note.getId());
Expand Down Expand Up @@ -367,8 +384,14 @@ public void moveNote(Account account) {
}

public interface NoteFragmentListener {
enum Mode {
EDIT, PREVIEW, DIRECT_EDIT
}

void close();

void onNoteUpdated(Note note);

void changeMode(@NonNull Mode mode, boolean reloadNote);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,20 @@
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.preference.PreferenceManager;

import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException;
import com.nextcloud.android.sso.helper.SingleAccountHelper;
import com.nextcloud.android.sso.model.SingleSignOnAccount;

import java.io.BufferedReader;
import java.io.IOException;
Expand All @@ -34,6 +37,7 @@
import it.niedermann.owncloud.notes.databinding.ActivityEditBinding;
import it.niedermann.owncloud.notes.edit.category.CategoryViewModel;
import it.niedermann.owncloud.notes.main.MainActivity;
import it.niedermann.owncloud.notes.persistence.NotesRepository;
import it.niedermann.owncloud.notes.persistence.entity.Account;
import it.niedermann.owncloud.notes.persistence.entity.Note;
import it.niedermann.owncloud.notes.shared.model.NavigationCategory;
Expand All @@ -57,11 +61,14 @@ public class EditNoteActivity extends LockedActivity implements BaseNoteFragment
private ActivityEditBinding binding;

private BaseNoteFragment fragment;
private NotesRepository repo;

@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

repo = NotesRepository.getInstance(getApplicationContext());

try {
if (SingleAccountHelper.getCurrentSingleSignOnAccount(this) == null) {
throw new NoCurrentAccountSelectedException();
Expand Down Expand Up @@ -118,9 +125,20 @@ private long getNoteId() {
}

private long getAccountId() {
return getIntent().getLongExtra(PARAM_ACCOUNT_ID, 0);
final long idParam = getIntent().getLongExtra(PARAM_ACCOUNT_ID, 0);
if (idParam == 0) {
try {
final SingleSignOnAccount ssoAcc = SingleAccountHelper.getCurrentSingleSignOnAccount(this);
return repo.getAccountByName(ssoAcc.name).getId();
} catch (NextcloudFilesAppAccountNotFoundException |
NoCurrentAccountSelectedException e) {
Log.w(TAG, "getAccountId: no current account", e);
}
}
return idParam;
}


/**
* Starts the note fragment for an existing note or a new note.
* The actual behavior is triggered by the activity's intent.
Expand All @@ -145,44 +163,109 @@ private void launchNoteFragment() {
* @param noteId ID of the existing note.
*/
private void launchExistingNote(long accountId, long noteId) {
final var prefKeyNoteMode = getString(R.string.pref_key_note_mode);
final var prefKeyLastMode = getString(R.string.pref_key_last_note_mode);
final var prefValueEdit = getString(R.string.pref_value_mode_edit);
final var prefValuePreview = getString(R.string.pref_value_mode_preview);
final var prefValueLast = getString(R.string.pref_value_mode_last);
launchExistingNote(accountId, noteId, null);
}

final var preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
final String mode = preferences.getString(prefKeyNoteMode, prefValueEdit);
final String lastMode = preferences.getString(prefKeyLastMode, prefValueEdit);
boolean editMode = true;
if (prefValuePreview.equals(mode) || (prefValueLast.equals(mode) && prefValuePreview.equals(lastMode))) {
editMode = false;
}
launchExistingNote(accountId, noteId, editMode);
private void launchExistingNote(long accountId, long noteId, @Nullable final String mode) {
launchExistingNote(accountId, noteId, mode, false);
}

/**
* Starts a {@link NoteEditFragment} or {@link NotePreviewFragment} for an existing note.
*
* @param noteId ID of the existing note.
* @param edit View-mode of the fragment:
* <code>true</code> for {@link NoteEditFragment},
* <code>false</code> for {@link NotePreviewFragment}.
* @param noteId ID of the existing note.
* @param mode View-mode of the fragment (pref value or null). If null will be chosen based on
* user preferences.
* @param discardState If true, the state of the fragment will be discarded and a new fragment will be created
*/
private void launchExistingNote(long accountId, long noteId, boolean edit) {
private void launchExistingNote(long accountId, long noteId, @Nullable final String mode, final boolean discardState) {
// save state of the fragment in order to resume with the same note and originalNote
Fragment.SavedState savedState = null;
if (fragment != null) {
savedState = getSupportFragmentManager().saveFragmentInstanceState(fragment);
runOnUiThread(() -> {
Fragment.SavedState savedState = null;
if (fragment != null && !discardState) {
savedState = getSupportFragmentManager().saveFragmentInstanceState(fragment);
}
fragment = getNoteFragment(accountId, noteId, mode);
if (savedState != null) {
fragment.setInitialSavedState(savedState);
}
replaceFragment();
});
}

private void replaceFragment() {
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container_view, fragment).commit();
if (!fragment.shouldShowToolbar()) {
binding.toolbar.setVisibility(View.GONE);
} else {
binding.toolbar.setVisibility(View.VISIBLE);
}
fragment = edit
? NoteEditFragment.newInstance(accountId, noteId)
: NotePreviewFragment.newInstance(accountId, noteId);
}


/**
* Returns the preferred mode for the account. If the mode is "remember last" the last mode is returned.
* If the mode is "direct edit" and the account does not support direct edit, the default mode is returned.
*/
private String getPreferenceMode(long accountId) {

final var prefKeyNoteMode = getString(R.string.pref_key_note_mode);
final var prefKeyLastMode = getString(R.string.pref_key_last_note_mode);
final var defaultMode = getString(R.string.pref_value_mode_edit);
final var prefValueLast = getString(R.string.pref_value_mode_last);
final var prefValueDirectEdit = getString(R.string.pref_value_mode_direct_edit);

if (savedState != null) {
fragment.setInitialSavedState(savedState);

final var preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
final String modePreference = preferences.getString(prefKeyNoteMode, defaultMode);

String effectiveMode = modePreference;
if (modePreference.equals(prefValueLast)) {
effectiveMode = preferences.getString(prefKeyLastMode, defaultMode);
}

if (effectiveMode.equals(prefValueDirectEdit)) {
final Account accountById = repo.getAccountById(accountId);
final var directEditAvailable = accountById != null && accountById.isDirectEditingAvailable();
if (!directEditAvailable) {
effectiveMode = defaultMode;
}
}

return effectiveMode;
}

private BaseNoteFragment getNoteFragment(long accountId, long noteId, final @Nullable String modePref) {

final var effectiveMode = modePref == null ? getPreferenceMode(accountId) : modePref;

final var prefValueEdit = getString(R.string.pref_value_mode_edit);
final var prefValueDirectEdit = getString(R.string.pref_value_mode_direct_edit);
final var prefValuePreview = getString(R.string.pref_value_mode_preview);

if (effectiveMode.equals(prefValueEdit)) {
return NoteEditFragment.newInstance(accountId, noteId);
} else if (effectiveMode.equals(prefValueDirectEdit)) {
return NoteDirectEditFragment.newInstance(accountId, noteId);
} else if (effectiveMode.equals(prefValuePreview)) {
return NotePreviewFragment.newInstance(accountId, noteId);
} else {
throw new IllegalStateException("Unknown note modePref: " + modePref);
}
}


@NonNull
private BaseNoteFragment getNewNoteFragment(Note newNote) {
final var mode = getPreferenceMode(getAccountId());

final var prefValueDirectEdit = getString(R.string.pref_value_mode_direct_edit);

if (mode.equals(prefValueDirectEdit)) {
return NoteDirectEditFragment.newInstanceWithNewNote(newNote);
} else {
return NoteEditFragment.newInstanceWithNewNote(newNote);
}
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container_view, fragment).commit();
}

/**
Expand Down Expand Up @@ -219,10 +302,11 @@ private void launchNewNote() {
content = "";
}
final var newNote = new Note(null, Calendar.getInstance(), NoteUtil.generateNonEmptyNoteTitle(content, this), content, categoryTitle, favorite, null);
fragment = NoteEditFragment.newInstanceWithNewNote(newNote);
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container_view, fragment).commit();
fragment = getNewNoteFragment(newNote);
replaceFragment();
}


private void launchReadonlyNote() {
final var intent = getIntent();
final var content = new StringBuilder();
Expand All @@ -238,7 +322,7 @@ private void launchReadonlyNote() {
}

fragment = NoteReadonlyFragment.newInstance(content.toString());
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container_view, fragment).commit();
replaceFragment();
}

@Override
Expand All @@ -260,10 +344,10 @@ public boolean onOptionsItemSelected(MenuItem item) {
close();
return true;
} else if (itemId == R.id.menu_preview) {
launchExistingNote(getAccountId(), getNoteId(), false);
changeMode(Mode.PREVIEW, false);
return true;
} else if (itemId == R.id.menu_edit) {
launchExistingNote(getAccountId(), getNoteId(), true);
changeMode(Mode.EDIT, false);
return true;
}
return super.onOptionsItemSelected(item);
Expand All @@ -281,8 +365,10 @@ public void close() {
final String prefKeyLastMode = getString(R.string.pref_key_last_note_mode);
if (fragment instanceof NoteEditFragment) {
preferences.edit().putString(prefKeyLastMode, getString(R.string.pref_value_mode_edit)).apply();
} else {
} else if (fragment instanceof NotePreviewFragment) {
preferences.edit().putString(prefKeyLastMode, getString(R.string.pref_value_mode_preview)).apply();
} else if (fragment instanceof NoteDirectEditFragment) {
preferences.edit().putString(prefKeyLastMode, getString(R.string.pref_value_mode_direct_edit)).apply();
}
fragment.onCloseNote();

Expand All @@ -308,6 +394,24 @@ public void onNoteUpdated(Note note) {
}
}

@Override
public void changeMode(@NonNull Mode mode, boolean reloadNote) {
switch (mode) {
case EDIT:
launchExistingNote(getAccountId(), getNoteId(), getString(R.string.pref_value_mode_edit), reloadNote);
break;
case PREVIEW:
launchExistingNote(getAccountId(), getNoteId(), getString(R.string.pref_value_mode_preview), reloadNote);
break;
case DIRECT_EDIT:
launchExistingNote(getAccountId(), getNoteId(), getString(R.string.pref_value_mode_direct_edit), reloadNote);
break;
default:
throw new IllegalStateException("Unknown mode: " + mode);
}
}


@Override
public void onAccountPicked(@NonNull Account account) {
fragment.moveNote(account);
Expand All @@ -318,4 +422,4 @@ public void applyBrand(int color) {
final var util = BrandingUtil.of(color, this);
util.notes.applyBrandToPrimaryToolbar(binding.appBar, binding.toolbar, colorAccent);
}
}
}
Loading