Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
feat: Add FAB to switch to rich editing mode from plain edit/preview
Signed-off-by: Álvaro Brey <[email protected]>
  • Loading branch information
AlvaroBrey committed Mar 7, 2023
commit 61a4399b7d1414005f78fbdf7505f6fae75ab54a
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,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 @@ -273,10 +274,17 @@ 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) {
}

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

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

void close();

void onNoteUpdated(Note note);

void setToolbarVisibility(boolean visible);

void changeMode(@NonNull Mode mode);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
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;
Expand Down Expand Up @@ -145,59 +146,65 @@ private void launchNoteFragment() {
*
* @param noteId ID of the existing note.
*/
// TODO this method is no longer needed
private void launchExistingNote(long accountId, long noteId) {
launchExistingNote(accountId, noteId, false);
launchExistingNote(accountId, noteId, null);
}


/**
* 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 mode View-mode of the fragment (pref value or null). If null will be chosen based on
* user preferences.
*/
// TODO remove edit param
private void launchExistingNote(long accountId, long noteId, boolean edit) {
private void launchExistingNote(long accountId, long noteId, @Nullable final String mode) {
// 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);
}
fragment = getNoteFragment(accountId, noteId);
fragment = getNoteFragment(accountId, noteId, mode);

if (savedState != null) {
fragment.setInitialSavedState(savedState);
}
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container_view, fragment).commit();
}

private BaseNoteFragment getNoteFragment(long accountId, long noteId) {
private String getPreferenceMode() {
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 prefValueDirectEdit = getString(R.string.pref_value_mode_direct_edit);
final var prefValuePreview = getString(R.string.pref_value_mode_preview);
final var defaultMode = getString(R.string.pref_value_mode_edit);
final var prefValueLast = getString(R.string.pref_value_mode_last);

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

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

return effectiveMode;
}

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

final var effectiveMode = modePref == null ? getPreferenceMode() : 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 mode: " + effectiveMode);
throw new IllegalStateException("Unknown note modePref: " + modePref);
}
}

Expand Down Expand Up @@ -234,7 +241,7 @@ private void launchNewNote() {
if (content == null) {
content = "";
}
// TODO handle with direct edit
// TODO handle with direct edit?
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();
Expand Down Expand Up @@ -273,15 +280,14 @@ public boolean onCreateOptionsMenu(Menu menu) {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
final int itemId = item.getItemId();
// TODO handle this? how do we switch modes now?
if (itemId == android.R.id.home) {
close();
return true;
} else if (itemId == R.id.menu_preview) {
launchExistingNote(getAccountId(), getNoteId(), false);
changeMode(Mode.PREVIEW);
return true;
} else if (itemId == R.id.menu_edit) {
launchExistingNote(getAccountId(), getNoteId(), true);
changeMode(Mode.EDIT);
return true;
}
return super.onOptionsItemSelected(item);
Expand Down Expand Up @@ -333,6 +339,26 @@ public void setToolbarVisibility(boolean visible) {
binding.toolbar.setVisibility(visible ? View.VISIBLE : View.GONE);
}

@Override
public void changeMode(@NonNull Mode mode) {
switch (mode) {
case EDIT:
launchExistingNote(getAccountId(), getNoteId(), getString(R.string.pref_value_mode_edit));
break;
case PREVIEW:
launchExistingNote(getAccountId(), getNoteId(), getString(R.string.pref_value_mode_preview));
break;
case DIRECT_EDIT:
// TODO deal with conflict when doing some edits and then changing to direct edit
// TODO deal with note not created yet on server
launchExistingNote(getAccountId(), getNoteId(), getString(R.string.pref_value_mode_direct_edit));
break;
default:
throw new IllegalStateException("Unknown mode: " + mode);
}
}


@Override
public void onAccountPicked(@NonNull Account account) {
fragment.moveNote(account);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;

import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
import com.google.android.material.floatingactionbutton.FloatingActionButton;

import it.niedermann.owncloud.notes.R;
Expand Down Expand Up @@ -103,6 +104,11 @@ protected FloatingActionButton getSearchPrevButton() {
return binding.searchPrev;
}

@Override
protected @NonNull ExtendedFloatingActionButton getDirectEditingButton() {
return binding.directEditing;
}

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import androidx.preference.PreferenceManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener;

import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException;
Expand Down Expand Up @@ -80,6 +81,11 @@ protected FloatingActionButton getSearchPrevButton() {
return binding.searchPrev;
}

@Override
protected @NonNull ExtendedFloatingActionButton getDirectEditingButton() {
return binding.directEditing;
}

@Override
protected Layout getLayout() {
binding.singleNoteContent.onPreDraw();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package it.niedermann.owncloud.notes.edit;

import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.text.Layout;
Expand All @@ -16,12 +15,14 @@
import androidx.annotation.Nullable;
import androidx.appcompat.widget.SearchView;

import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
import com.google.android.material.floatingactionbutton.FloatingActionButton;

import java.util.regex.Pattern;

import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.branding.BrandingUtil;
import it.niedermann.owncloud.notes.shared.util.ExtendedFabUtil;

public abstract class SearchableBaseNoteFragment extends BaseNoteFragment {

Expand Down Expand Up @@ -54,6 +55,38 @@ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
}
}

@Override
protected void onScroll(int scrollY, int oldScrollY) {
super.onScroll(scrollY, oldScrollY);
if (getSearchNextButton() == null || getSearchNextButton().getVisibility() != View.VISIBLE) {
final ExtendedFloatingActionButton directFab = getDirectEditingButton();
if (oldScrollY > 0 && scrollY > oldScrollY && directFab.isShown()) {
ExtendedFabUtil.setExtendedFabVisibility(getDirectEditingButton(), false);
} else if (scrollY < oldScrollY && !directFab.isShown()) {
ExtendedFabUtil.setExtendedFabVisibility(getDirectEditingButton(), true);
}
}
}

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// TODO don't show fab if direct editing not available
final ExtendedFloatingActionButton directEditingButton = getDirectEditingButton();
directEditingButton.setExtended(false);
directEditingButton.setOnLongClickListener(v -> {
if (directEditingButton.isExtended()) {
directEditingButton.shrink();
} else {
directEditingButton.extend();
}
return true;
});
directEditingButton.setOnClickListener(v -> {
listener.changeMode(NoteFragmentListener.Mode.DIRECT_EDIT);
});
}

@Override
public void onPrepareOptionsMenu(@NonNull Menu menu) {
super.onPrepareOptionsMenu(menu);
Expand Down Expand Up @@ -199,7 +232,12 @@ public void onSaveInstanceState(@NonNull Bundle outState) {

protected abstract FloatingActionButton getSearchPrevButton();

@NonNull
protected abstract ExtendedFloatingActionButton getDirectEditingButton();


private void showSearchFabs() {
ExtendedFabUtil.setExtendedFabVisibility(getDirectEditingButton(), false);
final var next = getSearchNextButton();
final var prev = getSearchPrevButton();
if (prev != null) {
Expand Down Expand Up @@ -291,5 +329,6 @@ public void applyBrand(int color) {
final var util = BrandingUtil.of(color, requireContext());
util.material.themeFAB(getSearchNextButton());
util.material.themeFAB(getSearchPrevButton());
util.material.themeExtendedFAB(getDirectEditingButton());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package it.niedermann.owncloud.notes.shared.util

import android.view.View
import android.view.animation.Animation
import android.view.animation.AnimationUtils
import com.google.android.material.R
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton

object ExtendedFabUtil {
@JvmStatic
public fun setExtendedFabVisibility(
extendedFab: ExtendedFloatingActionButton,
visibility: Boolean,
) {
if (visibility) {
extendedFab.show()
} else {
if (extendedFab.isExtended) {
extendedFab.hide()
} else {
if (extendedFab.animation == null) {
val animation = AnimationUtils.loadAnimation(
extendedFab.context,
R.anim.abc_shrink_fade_out_from_bottom,
)
animation.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation) {}
override fun onAnimationEnd(animation: Animation) {
extendedFab.visibility = View.GONE
}

override fun onAnimationRepeat(animation: Animation) {}
})
extendedFab.startAnimation(animation)
}
}
}
}
}
24 changes: 24 additions & 0 deletions app/src/main/res/drawable/ic_rich_editing.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!--
Copyright Andreas Gohr

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000000"
android:pathData="M3,7H9V13H3V7M3,3H21V5H3V3M21,7V9H11V7H21M21,11V13H11V11H21M3,15H17V17H3V15M3,19H21V21H3V19Z" />
</vector>
17 changes: 16 additions & 1 deletion app/src/main/res/layout/fragment_note_edit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,19 @@
app:backgroundTint="@color/defaultBrand"
app:srcCompat="@drawable/ic_keyboard_arrow_down_white_24dp"
tools:visibility="visible" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/direct_editing"
style="?attr/floatingActionButtonPrimaryStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/spacer_2x"
android:contentDescription="@string/noteMode_rich_edit"
android:text="@string/noteMode_rich_edit"
android:visibility="visible"
app:backgroundTint="@color/defaultBrand"
app:layout_anchor="@id/scrollView"
app:layout_anchorGravity="bottom|end"
app:icon="@drawable/ic_rich_editing" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Loading