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
wip: Edit note with webview
Signed-off-by: Álvaro Brey <[email protected]>
  • Loading branch information
AlvaroBrey committed Mar 7, 2023
commit a133f9a455c4405573ba822cb671145398c8cf39
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 @@ -71,7 +71,7 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego
private Note originalNote;
private int originalScrollY;
protected NotesRepository repo;
private NoteFragmentListener listener;
protected NoteFragmentListener listener;
private boolean titleModified = false;

protected boolean isNew = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.widget.Toast;

Expand Down Expand Up @@ -175,14 +176,15 @@ private void launchExistingNote(long accountId, long noteId, boolean edit) {
if (fragment != null) {
savedState = getSupportFragmentManager().saveFragmentInstanceState(fragment);
}
fragment = edit
? NoteEditFragment.newInstance(accountId, noteId)
: NotePreviewFragment.newInstance(accountId, noteId);
// TODO switch between the three modes, don't hardcode direct edit
fragment = NoteDirectEditFragment.newInstance(accountId, noteId);

if (savedState != null) {
fragment.setInitialSavedState(savedState);
}
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container_view, fragment).commit();
// TODO only hide toolbar in direct editing mode
binding.toolbar.setVisibility(View.GONE);
}

/**
Expand Down Expand Up @@ -318,4 +320,4 @@ public void applyBrand(int color) {
final var util = BrandingUtil.of(color, this);
util.notes.applyBrandToPrimaryToolbar(binding.appBar, binding.toolbar, colorAccent);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package it.niedermann.owncloud.notes.edit

import android.annotation.SuppressLint
import android.content.pm.ApplicationInfo
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.Menu
import android.view.View
import android.view.ViewGroup
import android.webkit.JavascriptInterface
import android.webkit.WebSettings
import android.webkit.WebView
import android.widget.ScrollView
import androidx.core.view.isVisible
import com.nextcloud.android.sso.helper.SingleAccountHelper
import io.reactivex.android.schedulers.AndroidSchedulers
import it.niedermann.owncloud.notes.BuildConfig
import it.niedermann.owncloud.notes.R
import it.niedermann.owncloud.notes.databinding.FragmentNoteDirectEditBinding
import it.niedermann.owncloud.notes.persistence.DirectEditingRepository
import it.niedermann.owncloud.notes.persistence.entity.Note
import it.niedermann.owncloud.notes.shared.model.ISyncCallback

class NoteDirectEditFragment : BaseNoteFragment() {
private var _binding: FragmentNoteDirectEditBinding? = null
private val binding: FragmentNoteDirectEditBinding
get() = _binding!!

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}

override fun onPrepareOptionsMenu(menu: Menu) {
super.onPrepareOptionsMenu(menu)
menu.findItem(R.id.menu_edit).isVisible = false
menu.findItem(R.id.menu_preview).isVisible = true
}

public override fun getScrollView(): ScrollView? {
return null
}

override fun scrollToY(y: Int) {
// do nothing
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
Log.d(TAG, "onCreateView() called")
_binding = FragmentNoteDirectEditBinding.inflate(inflater, container, false)
// TODO prepare webview
setupWebSettings(binding.noteWebview.settings)
binding.noteWebview.addJavascriptInterface(
DirectEditingMobileInterface(this),
"DirectEditingMobileInterface",
)
return binding.root
}

override fun onDestroyView() {
super.onDestroyView()
binding.noteWebview.destroy()
_binding = null
}

override fun onNoteLoaded(note: Note) {
super.onNoteLoaded(note)
Log.d(TAG, "onNoteLoaded() called with: note = $note")
// TODO get url and open in webview
val appContext = requireActivity().applicationContext
val repo = DirectEditingRepository.getInstance(appContext)
val account = SingleAccountHelper.getCurrentSingleSignOnAccount(appContext)
val disposable = repo.getDirectEditingUrl(account, note)
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ url ->
if (BuildConfig.DEBUG) {
Log.d(TAG, "onNoteLoaded: url = $url")
}
binding.noteWebview.loadUrl(url)
}, { throwable ->
// TODO handle error
Log.e(TAG, "onNoteLoaded:", throwable)
})
}

@SuppressLint("SetJavaScriptEnabled")
private fun setupWebSettings(webSettings: WebSettings) {
WebView.setWebContentsDebuggingEnabled(true)
// enable zoom
webSettings.setSupportZoom(true)
webSettings.builtInZoomControls = true
webSettings.displayZoomControls = false

// Non-responsive webs are zoomed out when loaded
webSettings.useWideViewPort = true
webSettings.loadWithOverviewMode = true

// user agent
webSettings.setUserAgentString("Mozilla/5.0 (Android) Nextcloud-android/3.23.0")

// no private data storing
webSettings.savePassword = false
webSettings.saveFormData = false

// disable local file access
webSettings.allowFileAccess = false

// enable javascript
webSettings.javaScriptEnabled = true
webSettings.domStorageEnabled = true

// caching disabled in debug mode
if (requireActivity().applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE !== 0) {
binding.noteWebview.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE)
}
}

/**
* Gets the current content of the EditText field in the UI.
*
* @return String of the current content.
*/
override fun getContent(): String {
// TODO what to do here?
return ""
}

override fun saveNote(callback: ISyncCallback?) {
// nothing, editor autosaves
}

override fun onCloseNote() {
// nothing!
// TODO sync note with server
}

override fun applyBrand(color: Int) {
// TODO check if any branding needed
// nothing for now
}

companion object {
private const val TAG = "NoteDirectEditFragment"

@JvmStatic
fun newInstance(accountId: Long, noteId: Long): BaseNoteFragment {
val fragment = NoteDirectEditFragment()
val args = Bundle()
args.putLong(PARAM_NOTE_ID, noteId)
args.putLong(PARAM_ACCOUNT_ID, accountId)
fragment.arguments = args
return fragment
}
}

private class DirectEditingMobileInterface(val noteDirectEditFragment: NoteDirectEditFragment) {
@JavascriptInterface
fun close() {
Log.d(TAG, "close() called")
// TODO callback interface or make this class anonymous
noteDirectEditFragment.close()
}

@JavascriptInterface
fun share() {
// TODO share note
// openShareDialog()
}

@JavascriptInterface
fun loaded() {
noteDirectEditFragment.onLoaded()
}
}

private fun close() {
listener.close()
}

private fun onLoaded() {
activity?.runOnUiThread {
binding.progress.isVisible = false
binding.noteWebview.isVisible = true
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,12 @@
import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.FAVORITES;
import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.RECENT;
import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.UNCATEGORIZED;
import static it.niedermann.owncloud.notes.shared.util.NotesColorUtil.contrastRatioIsSufficient;
import static it.niedermann.owncloud.notes.shared.util.SSOUtil.askForNewAccount;

import android.accounts.NetworkErrorException;
import android.animation.AnimatorInflater;
import android.app.SearchManager;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
Expand Down Expand Up @@ -50,7 +48,6 @@
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import com.nextcloud.android.common.ui.theme.utils.ColorRole;
import com.nextcloud.android.common.ui.util.PlatformThemeUtil;
import com.nextcloud.android.sso.AccountImporter;
import com.nextcloud.android.sso.exceptions.AccountImportCancelledException;
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
Expand All @@ -59,7 +56,6 @@
import com.nextcloud.android.sso.exceptions.TokenMismatchException;
import com.nextcloud.android.sso.exceptions.UnknownErrorException;
import com.nextcloud.android.sso.helper.SingleAccountHelper;
import com.nextcloud.android.sso.model.SingleSignOnAccount;

import java.net.HttpURLConnection;
import java.util.LinkedList;
Expand Down Expand Up @@ -95,7 +91,6 @@
import it.niedermann.owncloud.notes.persistence.ApiProvider;
import it.niedermann.owncloud.notes.persistence.CapabilitiesClient;
import it.niedermann.owncloud.notes.persistence.CapabilitiesWorker;
import it.niedermann.owncloud.notes.persistence.DirectEditingRepository;
import it.niedermann.owncloud.notes.persistence.entity.Account;
import it.niedermann.owncloud.notes.persistence.entity.Note;
import it.niedermann.owncloud.notes.shared.model.CategorySortingMethod;
Expand Down Expand Up @@ -762,23 +757,11 @@ public void onError(@NonNull Throwable t) {

@Override
public void onNoteClick(int position, View v) {
// TODO restore to previous behaviour, this is just for testing
final boolean hasCheckedItems = tracker.getSelection().size() > 0;
if (!hasCheckedItems) {
final var note = (Note) adapter.getItem(position);
// startActivity(new Intent(getApplicationContext(), EditNoteActivity.class)
// .putExtra(EditNoteActivity.PARAM_NOTE_ID, note.getId()));
try {
final SingleSignOnAccount account = SingleAccountHelper.getCurrentSingleSignOnAccount(getApplicationContext());
final DirectEditingRepository repository = DirectEditingRepository.getInstance(getApplicationContext());
final var supported = repository.isDirectEditingSupportedByServer(account).blockingGet();
Log.d(TAG, "onNoteClick: direct editing is supported by server: " + supported);
final var directEditingUrl = repository.getDirectEditingUrl(account, note).blockingGet();
Log.d(TAG, "onNoteClick: direct editing url: " + directEditingUrl);
} catch (NoCurrentAccountSelectedException |
NextcloudFilesAppAccountNotFoundException e) {
throw new RuntimeException(e);
}
startActivity(new Intent(getApplicationContext(), EditNoteActivity.class)
.putExtra(EditNoteActivity.PARAM_NOTE_ID, note.getId()));
}
}

Expand Down
23 changes: 23 additions & 0 deletions app/src/main/res/layout/fragment_note_direct_edit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent">

<WebView
android:visibility="gone"
android:id="@+id/note_webview"
android:layout_width="match_parent"
android:layout_height="match_parent" />

<ProgressBar
android:id="@+id/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<!-- TODO FABs? Search?-->
</androidx.constraintlayout.widget.ConstraintLayout>