diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/NotesListViewItemTouchHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/android/NotesListViewItemTouchHelper.java index ba239af4..3e851ba5 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/NotesListViewItemTouchHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/NotesListViewItemTouchHelper.java @@ -10,9 +10,6 @@ import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.snackbar.Snackbar; -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 it.niedermann.owncloud.notes.R; @@ -27,6 +24,7 @@ public class NotesListViewItemTouchHelper extends ItemTouchHelper { private static final String TAG = NotesListViewItemTouchHelper.class.getCanonicalName(); public NotesListViewItemTouchHelper( + SingleSignOnAccount ssoAccount, Context context, ViewProvider viewProvider, NoteSQLiteOpenHelper db, @@ -61,36 +59,31 @@ public class NotesListViewItemTouchHelper extends ItemTouchHelper { */ @Override public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { - try { - SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(context); - switch (direction) { - case ItemTouchHelper.LEFT: { - final DBNote dbNoteWithoutContent = (DBNote) adapter.getItem(viewHolder.getAdapterPosition()); - final DBNote dbNote = db.getNote(dbNoteWithoutContent.getAccountId(), dbNoteWithoutContent.getId()); - db.deleteNoteAndSync(ssoAccount, dbNote.getId()); - adapter.remove(dbNote); - refreshLists.run(); - Log.v(TAG, "Item deleted through swipe ----------------------------------------------"); - Snackbar.make(viewProvider.getView(), context.getString(R.string.action_note_deleted, dbNote.getTitle()), Snackbar.LENGTH_LONG) - .setAction(R.string.action_undo, (View v) -> { - db.getNoteServerSyncHelper().addCallbackPush(ssoAccount, refreshLists::run); - db.addNoteAndSync(ssoAccount, dbNote.getAccountId(), dbNote); - refreshLists.run(); - Snackbar.make(viewProvider.getView(), context.getString(R.string.action_note_restored, dbNote.getTitle()), Snackbar.LENGTH_SHORT) - .show(); - }) - .show(); - break; - } - case ItemTouchHelper.RIGHT: { - final DBNote dbNote = (DBNote) adapter.getItem(viewHolder.getAdapterPosition()); - db.toggleFavorite(ssoAccount, dbNote, syncCallBack); - refreshLists.run(); - break; - } + switch (direction) { + case ItemTouchHelper.LEFT: { + final DBNote dbNoteWithoutContent = (DBNote) adapter.getItem(viewHolder.getAdapterPosition()); + final DBNote dbNote = db.getNote(dbNoteWithoutContent.getAccountId(), dbNoteWithoutContent.getId()); + db.deleteNoteAndSync(ssoAccount, dbNote.getId()); + adapter.remove(dbNote); + refreshLists.run(); + Log.v(TAG, "Item deleted through swipe ----------------------------------------------"); + Snackbar.make(viewProvider.getView(), context.getString(R.string.action_note_deleted, dbNote.getTitle()), Snackbar.LENGTH_LONG) + .setAction(R.string.action_undo, (View v) -> { + db.getNoteServerSyncHelper().addCallbackPush(ssoAccount, refreshLists::run); + db.addNoteAndSync(ssoAccount, dbNote.getAccountId(), dbNote); + refreshLists.run(); + Snackbar.make(viewProvider.getView(), context.getString(R.string.action_note_restored, dbNote.getTitle()), Snackbar.LENGTH_SHORT) + .show(); + }) + .show(); + break; + } + case ItemTouchHelper.RIGHT: { + final DBNote dbNote = (DBNote) adapter.getItem(viewHolder.getAdapterPosition()); + db.toggleFavorite(ssoAccount, dbNote, syncCallBack); + refreshLists.run(); + break; } - } catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) { - e.printStackTrace(); } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java index 092ede4b..fee85762 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java @@ -265,7 +265,7 @@ public class EditNoteActivity extends AppCompatActivity implements BaseNoteFragm } } else { // Maybe account is not authenticated -> note == null - Log.e(TAG, "note is null, start NotesListViewActivity"); + Log.e(TAG, "note is null, start " + NotesListViewActivity.class.getSimpleName()); startActivity(new Intent(this, NotesListViewActivity.class)); finish(); } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java index 718194d4..77333f51 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java @@ -191,18 +191,22 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap selectAccount(ssoAccount.name); } } catch (NoCurrentAccountSelectedException | NextcloudFilesAppAccountNotFoundException e) { + if (localAccount == null) { + List localAccounts = db.getAccounts(); + if (localAccounts.size() > 0) { + localAccount = localAccounts.get(0); + } + } if (!notAuthorizedAccountHandled) { handleNotAuthorizedAccount(); } } // refresh and sync every time the activity gets + refreshLists(); if (localAccount != null) { - refreshLists(); + synchronize(); db.getNoteServerSyncHelper().addCallbackPull(ssoAccount, syncCallBack); - if (db.getNoteServerSyncHelper().isSyncPossible()) { - synchronize(); - } } super.onResume(); } @@ -232,18 +236,19 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap private void selectAccount(String accountName) { fabCreate.hide(); SingleAccountHelper.setCurrentAccount(getApplicationContext(), accountName); + localAccount = db.getLocalAccountByAccountName(accountName); try { ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(getApplicationContext()); - localAccount = db.getLocalAccountByAccountName(accountName); synchronize(); - refreshLists(); - fabCreate.show(); - setupHeader(); - setupNavigationList(ADAPTER_KEY_RECENT); - updateUsernameInDrawer(); } catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) { - e.printStackTrace(); + Log.i(TAG, "Tried to select account, but got an " + e.getClass().getSimpleName() + ". Asking for importing an account..."); + handleNotAuthorizedAccount(); } + refreshLists(); + fabCreate.show(); + setupHeader(); + setupNavigationList(ADAPTER_KEY_RECENT); + updateUsernameInDrawer(); } private void handleNotAuthorizedAccount() { @@ -316,13 +321,12 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap } }); - // Pull to Refresh swipeRefreshLayout.setOnRefreshListener(() -> { - if (db.getNoteServerSyncHelper().isSyncPossible()) { - synchronize(); - } else { + if (ssoAccount == null) { swipeRefreshLayout.setRefreshing(false); - Snackbar.make(coordinatorLayout, getString(R.string.error_sync, getString(LoginStatus.NO_NETWORK.str)), Snackbar.LENGTH_LONG).show(); + askForNewAccount(this); + } else { + synchronize(); } }); @@ -533,7 +537,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap adapter = new ItemAdapter(this); listView.setAdapter(adapter); listView.setLayoutManager(new LinearLayoutManager(this)); - new NotesListViewItemTouchHelper(this, this, db, adapter, syncCallBack, this::refreshLists).attachToRecyclerView(listView); + new NotesListViewItemTouchHelper(ssoAccount, this, this, db, adapter, syncCallBack, this::refreshLists).attachToRecyclerView(listView); } private void refreshLists() { @@ -803,11 +807,23 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap } private void synchronize() { - swipeRefreshLayout.setRefreshing(true); - db.getNoteServerSyncHelper().addCallbackPull(ssoAccount, syncCallBack); - db.getNoteServerSyncHelper().scheduleSync(ssoAccount, false); + NoteServerSyncHelper syncHelper = db.getNoteServerSyncHelper(); + if (syncHelper.isSyncPossible()) { + swipeRefreshLayout.setRefreshing(true); + syncHelper.addCallbackPull(ssoAccount, syncCallBack); + syncHelper.scheduleSync(ssoAccount, false); + } else { // Sync is not possible + swipeRefreshLayout.setRefreshing(false); + if (syncHelper.isNetworkConnected() && syncHelper.isSyncOnlyOnWifi()) { + Log.d(TAG, "Network is connected, but sync is not possible"); + } else { + Log.d(TAG, "Sync is not possible, because network is not connected"); + Snackbar.make(coordinatorLayout, getString(R.string.error_sync, getString(LoginStatus.NO_NETWORK.str)), Snackbar.LENGTH_LONG).show(); + } + } } + @Override public void onAccountChosen(LocalAccount account) { List selection = new ArrayList<>(adapter.getSelected()); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NotePreviewFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NotePreviewFragment.java index d2a5f288..c85eb8a0 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NotePreviewFragment.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NotePreviewFragment.java @@ -45,6 +45,7 @@ import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; import it.niedermann.owncloud.notes.util.DisplayUtils; import it.niedermann.owncloud.notes.util.MarkDownUtil; import it.niedermann.owncloud.notes.util.NoteLinksUtils; +import it.niedermann.owncloud.notes.util.SSOUtil; public class NotePreviewFragment extends SearchableBaseNoteFragment implements OnRefreshListener { @@ -210,7 +211,7 @@ public class NotePreviewFragment extends SearchableBaseNoteFragment implements O @Override public void onRefresh() { - if (db.getNoteServerSyncHelper().isSyncPossible()) { + if (db.getNoteServerSyncHelper().isSyncPossible() && SSOUtil.isConfigured(getContext())) { swipeRefreshLayout.setRefreshing(true); try { SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(getContext()); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java index 84246185..6d5c3492 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java @@ -15,6 +15,7 @@ import android.util.Log; import android.view.View; import android.widget.Toast; +import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; @@ -47,6 +48,7 @@ import it.niedermann.owncloud.notes.model.ISyncCallback; import it.niedermann.owncloud.notes.model.LocalAccount; import it.niedermann.owncloud.notes.model.LoginStatus; import it.niedermann.owncloud.notes.util.ExceptionUtil; +import it.niedermann.owncloud.notes.util.SSOUtil; import it.niedermann.owncloud.notes.util.ServerResponse; import static android.content.Context.CLIPBOARD_SERVICE; @@ -64,6 +66,7 @@ public class NoteServerSyncHelper { private Context context; // Track network connection changes using a BroadcastReceiver + private boolean isSyncPossible = false; private boolean networkConnected = false; private String syncOnlyOnWifiKey; private boolean syncOnlyOnWifi; @@ -82,7 +85,7 @@ public class NoteServerSyncHelper { @Override public void onReceive(Context context, Intent intent) { updateNetworkStatus(); - if (isSyncPossible()) { + if (isSyncPossible() && SSOUtil.isConfigured(context)) { try { scheduleSync(SingleAccountHelper.getCurrentSingleSignOnAccount(context), false); } catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) { @@ -101,7 +104,6 @@ public class NoteServerSyncHelper { private Map> callbacksPush = new HashMap<>(); private Map> callbacksPull = new HashMap<>(); - private NoteServerSyncHelper(NoteSQLiteOpenHelper db) { this.db = db; this.context = db.getContext(); @@ -139,27 +141,26 @@ public class NoteServerSyncHelper { super.finalize(); } - private static boolean isConfigured(Context context) { - try { - SingleAccountHelper.getCurrentSingleSignOnAccount(context); - return true; - } catch (NextcloudFilesAppAccountNotFoundException e) { - return false; - } catch (NoCurrentAccountSelectedException e) { - return false; - } - } - /** - * Synchronization is only possible, if there is an active network connection and - * SingleSignOn is available + * Synchronization is only possible, if there is an active network connection. + * + * This method respects the user preference "Sync on Wi-Fi only". + * * NoteServerSyncHelper observes changes in the network connection. * The current state can be retrieved with this method. * * @return true if sync is possible, otherwise false. */ public boolean isSyncPossible() { - return networkConnected && isConfigured(context.getApplicationContext()); + return isSyncPossible; + } + + public boolean isNetworkConnected() { + return networkConnected; + } + + public boolean isSyncOnlyOnWifi() { + return syncOnlyOnWifi; } /** @@ -171,10 +172,16 @@ public class NoteServerSyncHelper { * @param callback Implementation of ISyncCallback, contains one method that shall be executed. */ public void addCallbackPush(SingleSignOnAccount ssoAccount, ISyncCallback callback) { - if (!callbacksPush.containsKey(ssoAccount.name)) { - callbacksPush.put(ssoAccount.name, new ArrayList<>()); + if (ssoAccount == null) { + Log.i(TAG, "ssoAccount is null. Is this a local account?"); + callback.onScheduled(); + callback.onFinish(); + } else { + if (!callbacksPush.containsKey(ssoAccount.name)) { + callbacksPush.put(ssoAccount.name, new ArrayList<>()); + } + Objects.requireNonNull(callbacksPush.get(ssoAccount.name)).add(callback); } - Objects.requireNonNull(callbacksPush.get(ssoAccount.name)).add(callback); } /** @@ -186,10 +193,16 @@ public class NoteServerSyncHelper { * @param callback Implementation of ISyncCallback, contains one method that shall be executed. */ public void addCallbackPull(SingleSignOnAccount ssoAccount, ISyncCallback callback) { - if (!callbacksPull.containsKey(ssoAccount.name)) { - callbacksPull.put(ssoAccount.name, new ArrayList<>()); + if (ssoAccount == null) { + Log.i(TAG, "ssoAccount is null. Is this a local account?"); + callback.onScheduled(); + callback.onFinish(); + } else { + if (!callbacksPull.containsKey(ssoAccount.name)) { + callbacksPull.put(ssoAccount.name, new ArrayList<>()); + } + Objects.requireNonNull(callbacksPull.get(ssoAccount.name)).add(callback); } - Objects.requireNonNull(callbacksPull.get(ssoAccount.name)).add(callback); } @@ -200,33 +213,37 @@ public class NoteServerSyncHelper { * @param onlyLocalChanges Whether to only push local changes to the server or to also load the whole list of notes from the server. */ public void scheduleSync(SingleSignOnAccount ssoAccount, boolean onlyLocalChanges) { - if (syncActive.get(ssoAccount.name) == null) { - syncActive.put(ssoAccount.name, false); - } - Log.d(TAG, "Sync requested (" + (onlyLocalChanges ? "onlyLocalChanges" : "full") + "; " + (syncActive.get(ssoAccount.name) ? "sync active" : "sync NOT active") + ") ..."); - if (isSyncPossible() && (!syncActive.get(ssoAccount.name) || onlyLocalChanges)) { - Log.d(TAG, "... starting now"); - SyncTask syncTask = new SyncTask(db.getLocalAccountByAccountName(ssoAccount.name), ssoAccount, onlyLocalChanges); - syncTask.addCallbacks(ssoAccount, callbacksPush.get(ssoAccount.name)); - callbacksPush.put(ssoAccount.name, new ArrayList<>()); - if (!onlyLocalChanges) { - syncTask.addCallbacks(ssoAccount, callbacksPull.get(ssoAccount.name)); - callbacksPull.put(ssoAccount.name, new ArrayList<>()); - } - syncTask.execute(); - } else if (!onlyLocalChanges) { - Log.d(TAG, "... scheduled"); - syncScheduled.put(ssoAccount.name, true); - if(callbacksPush.containsKey(ssoAccount.name) && callbacksPush.get(ssoAccount.name) != null) { - for (ISyncCallback callback : callbacksPush.get(ssoAccount.name)) { - callback.onScheduled(); - } - } + if (ssoAccount == null) { + Log.i(TAG, "ssoAccount is null. Is this a local account?"); } else { - Log.d(TAG, "... do nothing"); - if(callbacksPull.containsKey(ssoAccount.name) && callbacksPull.get(ssoAccount.name) != null) { - for (ISyncCallback callback : callbacksPush.get(ssoAccount.name)) { - callback.onScheduled(); + if (syncActive.get(ssoAccount.name) == null) { + syncActive.put(ssoAccount.name, false); + } + Log.d(TAG, "Sync requested (" + (onlyLocalChanges ? "onlyLocalChanges" : "full") + "; " + (syncActive.get(ssoAccount.name) ? "sync active" : "sync NOT active") + ") ..."); + if (isSyncPossible() && (!syncActive.get(ssoAccount.name) || onlyLocalChanges)) { + Log.d(TAG, "... starting now"); + SyncTask syncTask = new SyncTask(db.getLocalAccountByAccountName(ssoAccount.name), ssoAccount, onlyLocalChanges); + syncTask.addCallbacks(ssoAccount, callbacksPush.get(ssoAccount.name)); + callbacksPush.put(ssoAccount.name, new ArrayList<>()); + if (!onlyLocalChanges) { + syncTask.addCallbacks(ssoAccount, callbacksPull.get(ssoAccount.name)); + callbacksPull.put(ssoAccount.name, new ArrayList<>()); + } + syncTask.execute(); + } else if (!onlyLocalChanges) { + Log.d(TAG, "... scheduled"); + syncScheduled.put(ssoAccount.name, true); + if (callbacksPush.containsKey(ssoAccount.name) && callbacksPush.get(ssoAccount.name) != null) { + for (ISyncCallback callback : callbacksPush.get(ssoAccount.name)) { + callback.onScheduled(); + } + } + } else { + Log.d(TAG, "... do nothing"); + if (callbacksPull.containsKey(ssoAccount.name) && callbacksPull.get(ssoAccount.name) != null) { + for (ISyncCallback callback : callbacksPush.get(ssoAccount.name)) { + callback.onScheduled(); + } } } } @@ -237,18 +254,20 @@ public class NoteServerSyncHelper { NetworkInfo activeInfo = connMgr.getActiveNetworkInfo(); if (activeInfo != null && activeInfo.isConnected()) { - networkConnected = + networkConnected = true; + isSyncPossible = !syncOnlyOnWifi || ((ConnectivityManager) context.getApplicationContext() .getSystemService(Context.CONNECTIVITY_SERVICE)) .getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnected(); - if (networkConnected) { + if (isSyncPossible) { Log.d(TAG, "Network connection established."); } else { Log.d(TAG, "Network connected, but not used because only synced on wifi."); } } else { networkConnected = false; + isSyncPossible = false; Log.d(TAG, "No network connection."); } } @@ -264,7 +283,7 @@ public class NoteServerSyncHelper { private final Map> callbacks = new HashMap<>(); private List exceptions = new ArrayList<>(); - SyncTask(LocalAccount localAccount, SingleSignOnAccount ssoAccount, boolean onlyLocalChanges) { + SyncTask(@NonNull LocalAccount localAccount, @NonNull SingleSignOnAccount ssoAccount, boolean onlyLocalChanges) { this.localAccount = localAccount; this.ssoAccount = ssoAccount; this.onlyLocalChanges = onlyLocalChanges; diff --git a/app/src/main/java/it/niedermann/owncloud/notes/util/SSOUtil.java b/app/src/main/java/it/niedermann/owncloud/notes/util/SSOUtil.java index 6759d223..5089f14d 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/util/SSOUtil.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/util/SSOUtil.java @@ -1,13 +1,17 @@ package it.niedermann.owncloud.notes.util; import android.app.Activity; +import android.content.Context; import android.util.Log; import androidx.annotation.NonNull; import com.nextcloud.android.sso.AccountImporter; import com.nextcloud.android.sso.exceptions.AndroidGetAccountsPermissionNotGranted; +import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException; import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotInstalledException; +import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException; +import com.nextcloud.android.sso.helper.SingleAccountHelper; import com.nextcloud.android.sso.ui.UiExceptionManager; public class SSOUtil { @@ -36,4 +40,15 @@ public class SSOUtil { AccountImporter.requestAndroidAccountPermissionsAndPickAccount(activity); } } + + public static boolean isConfigured(Context context) { + try { + SingleAccountHelper.getCurrentSingleSignOnAccount(context); + return true; + } catch (NextcloudFilesAppAccountNotFoundException e) { + return false; + } catch (NoCurrentAccountSelectedException e) { + return false; + } + } }