Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/stefan-niedermann/nextcloud-notes.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Niedermann <info@niedermann.it>2021-06-28 11:06:50 +0300
committerStefan Niedermann <info@niedermann.it>2021-06-28 11:06:50 +0300
commit9fe1b079efa58a822c3432859abbc9565739543b (patch)
tree7dd87a5a9b838832374084d20770d20b2d4ff3d5 /app/src/main/java/it
parent0d1136aad3cae384e7a9c636f79e4d121b28dd89 (diff)
parent7c5cb1b2552ee711eeaec98e3e8928e8e033cb0a (diff)
Merge branch 'master' into 916-settings
# Conflicts: # app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java # app/src/main/java/it/niedermann/owncloud/notes/shared/model/ApiVersion.java # app/src/main/res/values/strings.xml
Diffstat (limited to 'app/src/main/java/it')
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/AppendToNoteActivity.java2
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/accountswitcher/AccountSwitcherDialog.java2
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/accountswitcher/AccountSwitcherViewHolder.java2
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/edit/BaseNoteFragment.java35
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/edit/NoteEditFragment.java41
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/edit/NotePreviewFragment.java8
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/importaccount/ImportAccountActivity.java29
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/importaccount/ImportAccountViewModel.java5
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/main/MainActivity.java159
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/main/MainViewModel.java70
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/main/MultiSelectedActionModeCallback.java15
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/main/items/grid/GridItemDecoration.java3
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/main/menu/MenuAdapter.java55
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/main/menu/MenuViewHolder.java6
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/manageaccounts/ManageAccountsViewModel.java11
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/ApiProvider.java28
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/CapabilitiesClient.java36
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/CapabilitiesWorker.java5
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesDatabase.java8
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java112
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesServerSyncTask.java70
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/SyncWorker.java25
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/AccountDao.java12
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/NoteDao.java13
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/Account.java40
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/Note.java1
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_14_15.java20
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_18_19.java8
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_21_22.java43
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_22_23.java101
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/CapabilitiesDeserializer.java84
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/NotesAPI.java8
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/OcsAPI.java9
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/preferences/PreferencesActivity.java10
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/preferences/PreferencesFragment.java36
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/preferences/PreferencesViewModel.java9
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/shared/account/AccountChooserViewHolder.java2
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/shared/model/ApiVersion.java7
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/shared/model/Capabilities.java86
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/shared/model/CategorySortingMethod.java8
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/shared/model/OcsResponse.java30
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/shared/model/OcsUser.java16
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/shared/util/ApiVersionUtil.java105
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/shared/util/CustomAppGlideModule.java19
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/shared/util/DatabaseIndexUtil.java41
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/shared/util/DeviceCredentialUtil.java2
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/shared/util/DisplayUtils.java47
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/shared/util/NoteUtil.java4
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/shared/util/NotesColorUtil.java1
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/shared/util/SSOUtil.java2
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/shared/util/ShareUtil.java5
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/shared/util/SupportUtil.java2
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListWidget.java5
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListWidgetConfigurationActivity.java13
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/widget/singlenote/SingleNoteWidget.java6
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/widget/singlenote/SingleNoteWidgetConfigurationActivity.java4
56 files changed, 959 insertions, 567 deletions
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/AppendToNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/AppendToNoteActivity.java
index fc4b8fc6..29d2d0ad 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/AppendToNoteActivity.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/AppendToNoteActivity.java
@@ -42,7 +42,7 @@ public class AppendToNoteActivity extends MainActivity {
fullNote$.removeObservers(this);
final String oldContent = fullNote.getContent();
String newContent;
- if (oldContent != null && oldContent.length() > 0) {
+ if (!TextUtils.isEmpty(oldContent)) {
newContent = oldContent + "\n\n" + receivedText;
} else {
newContent = receivedText;
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/accountswitcher/AccountSwitcherDialog.java b/app/src/main/java/it/niedermann/owncloud/notes/accountswitcher/AccountSwitcherDialog.java
index f5643d86..e1ba3e8e 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/accountswitcher/AccountSwitcherDialog.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/accountswitcher/AccountSwitcherDialog.java
@@ -67,7 +67,7 @@ public class AccountSwitcherDialog extends BrandedDialogFragment {
account$.observe(requireActivity(), (currentLocalAccount) -> {
account$.removeObservers(requireActivity());
- binding.accountName.setText(currentLocalAccount.getUserName());
+ binding.accountName.setText(currentLocalAccount.getDisplayName());
binding.accountHost.setText(Uri.parse(currentLocalAccount.getUrl()).getHost());
Glide.with(requireContext())
.load(currentLocalAccount.getUrl() + "/index.php/avatar/" + Uri.encode(currentLocalAccount.getUserName()) + "/64")
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/accountswitcher/AccountSwitcherViewHolder.java b/app/src/main/java/it/niedermann/owncloud/notes/accountswitcher/AccountSwitcherViewHolder.java
index db04e0db..1f096c96 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/accountswitcher/AccountSwitcherViewHolder.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/accountswitcher/AccountSwitcherViewHolder.java
@@ -25,7 +25,7 @@ public class AccountSwitcherViewHolder extends RecyclerView.ViewHolder {
}
public void bind(@NonNull Account localAccount, @NonNull Consumer<Account> onAccountClick) {
- binding.accountName.setText(localAccount.getUserName());
+ binding.accountName.setText(localAccount.getDisplayName());
binding.accountHost.setText(Uri.parse(localAccount.getUrl()).getHost());
Glide.with(itemView.getContext())
.load(new SingleSignOnUrl(localAccount.getAccountName(), localAccount.getUrl() + "/index.php/avatar/" + Uri.encode(localAccount.getUserName()) + "/64"))
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/edit/BaseNoteFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/edit/BaseNoteFragment.java
index b1cf1d54..4fc883ac 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/edit/BaseNoteFragment.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/edit/BaseNoteFragment.java
@@ -24,6 +24,7 @@ import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
+import androidx.lifecycle.LiveData;
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException;
@@ -32,6 +33,8 @@ import com.nextcloud.android.sso.model.SingleSignOnAccount;
import java.util.ArrayList;
import java.util.Calendar;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import it.niedermann.android.util.ColorUtil;
import it.niedermann.owncloud.notes.R;
@@ -47,6 +50,7 @@ import it.niedermann.owncloud.notes.persistence.entity.Note;
import it.niedermann.owncloud.notes.shared.model.ApiVersion;
import it.niedermann.owncloud.notes.shared.model.DBStatus;
import it.niedermann.owncloud.notes.shared.model.ISyncCallback;
+import it.niedermann.owncloud.notes.shared.util.ApiVersionUtil;
import it.niedermann.owncloud.notes.shared.util.NoteUtil;
import it.niedermann.owncloud.notes.shared.util.NotesColorUtil;
import it.niedermann.owncloud.notes.shared.util.ShareUtil;
@@ -60,6 +64,7 @@ import static java.lang.Boolean.TRUE;
public abstract class BaseNoteFragment extends BrandedFragment implements CategoryDialogListener, EditTitleListener {
private static final String TAG = BaseNoteFragment.class.getSimpleName();
+ protected final ExecutorService executor = Executors.newCachedThreadPool();
protected static final int MENU_ID_PIN = -1;
public static final String PARAM_NOTE_ID = "noteId";
@@ -94,9 +99,9 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego
}
@Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- new Thread(() -> {
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ executor.submit(() -> {
try {
SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(requireContext().getApplicationContext());
this.localAccount = repo.getAccountByName(ssoAccount.name);
@@ -141,7 +146,7 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego
} catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
e.printStackTrace();
}
- }).start();
+ });
setHasOptionsMenu(true);
}
@@ -193,7 +198,8 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego
if (note != null) {
prepareFavoriteOption(menu.findItem(R.id.menu_favorite));
- menu.findItem(R.id.menu_title).setVisible(localAccount.getPreferredApiVersion() != null && localAccount.getPreferredApiVersion().compareTo(ApiVersion.API_VERSION_1_0) >= 0);
+ final ApiVersion preferredApiVersion = ApiVersionUtil.getPreferredApiVersion(localAccount.getApiVersion());
+ menu.findItem(R.id.menu_title).setVisible(preferredApiVersion != null && preferredApiVersion.compareTo(ApiVersion.API_VERSION_1_0) >= 0);
menu.findItem(R.id.menu_delete).setVisible(!isNew);
}
}
@@ -211,13 +217,13 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego
public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId();
if (itemId == R.id.menu_cancel) {
- new Thread(() -> {
+ executor.submit(() -> {
if (originalNote == null) {
repo.deleteNoteAndSync(localAccount, note.getId());
} else {
repo.updateNoteAndSync(localAccount, originalNote, null, null, null);
}
- }).start();
+ });
listener.close();
return true;
} else if (itemId == R.id.menu_delete) {
@@ -236,11 +242,9 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego
showEditTitleDialog();
return true;
} else if (itemId == R.id.menu_move) {
- new Thread(() -> {
- AccountPickerDialogFragment
- .newInstance(new ArrayList<>(), note.getAccountId())
- .show(requireActivity().getSupportFragmentManager(), BaseNoteFragment.class.getSimpleName());
- }).start();
+ executor.submit(() -> AccountPickerDialogFragment
+ .newInstance(new ArrayList<>(repo.getAccounts()), note.getAccountId())
+ .show(requireActivity().getSupportFragmentManager(), BaseNoteFragment.class.getSimpleName()));
return true;
} else if (itemId == R.id.menu_share) {
ShareUtil.openShareDialog(requireContext(), note.getTitle(), note.getContent());
@@ -363,14 +367,15 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego
public void onTitleEdited(String newTitle) {
titleModified = true;
note.setTitle(newTitle);
- new Thread(() -> {
+ executor.submit(() -> {
note = repo.updateNoteAndSync(localAccount, note, note.getContent(), newTitle, null);
requireActivity().runOnUiThread(() -> listener.onNoteUpdated(note));
- }).start();
+ });
}
public void moveNote(Account account) {
- repo.moveNoteToAnotherAccount(account, note);
+ final LiveData<Note> moveLiveData = repo.moveNoteToAnotherAccount(account, note);
+ moveLiveData.observe(this, (v) -> moveLiveData.removeObservers(this));
listener.close();
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/edit/NoteEditFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/edit/NoteEditFragment.java
index 298a6c3f..83c8eb1a 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/edit/NoteEditFragment.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/edit/NoteEditFragment.java
@@ -29,6 +29,7 @@ import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.databinding.FragmentNoteEditBinding;
import it.niedermann.owncloud.notes.persistence.entity.Note;
import it.niedermann.owncloud.notes.shared.model.ISyncCallback;
+import it.niedermann.owncloud.notes.shared.util.DisplayUtils;
import static androidx.core.view.ViewCompat.isAttachedToWindow;
import static it.niedermann.owncloud.notes.shared.util.NoteUtil.getFontSizeFromPreferences;
@@ -59,6 +60,7 @@ public class NoteEditFragment extends SearchableBaseNoteFragment {
}
};
private TextWatcher textWatcher;
+ private boolean keyboardShown = false;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
@@ -138,22 +140,17 @@ public class NoteEditFragment extends SearchableBaseNoteFragment {
public void onResume() {
super.onResume();
binding.editContent.addTextChangedListener(textWatcher);
+
+ if (keyboardShown) {
+ openSoftKeyboard();
+ }
}
@Override
protected void onNoteLoaded(Note note) {
super.onNoteLoaded(note);
if (TextUtils.isEmpty(note.getContent())) {
- binding.editContent.post(() -> {
- binding.editContent.requestFocus();
-
- final InputMethodManager imm = (InputMethodManager) requireContext().getSystemService(Context.INPUT_METHOD_SERVICE);
- if (imm != null) {
- imm.showSoftInput(binding.editContent, InputMethodManager.SHOW_IMPLICIT);
- } else {
- Log.e(TAG, InputMethodManager.class.getSimpleName() + " is null.");
- }
- });
+ openSoftKeyboard();
}
binding.editContent.setMarkdownString(note.getContent());
@@ -166,11 +163,32 @@ public class NoteEditFragment extends SearchableBaseNoteFragment {
}
}
+ private void openSoftKeyboard() {
+ binding.editContent.postDelayed(() -> {
+ binding.editContent.requestFocus();
+
+ final InputMethodManager imm = (InputMethodManager) requireContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ if (imm != null) {
+ imm.showSoftInput(binding.editContent, InputMethodManager.SHOW_IMPLICIT);
+ } else {
+ Log.e(TAG, InputMethodManager.class.getSimpleName() + " is null.");
+ }
+ //Without a small delay the keyboard does not show reliably
+ }, 100);
+ }
+
@Override
public void onPause() {
super.onPause();
binding.editContent.removeTextChangedListener(textWatcher);
cancelTimers();
+
+ final ViewGroup parentView = requireActivity().findViewById(android.R.id.content);
+ if (parentView != null && parentView.getChildCount() > 0) {
+ keyboardShown = DisplayUtils.isSoftKeyboardVisible(parentView.getChildAt(0));
+ } else {
+ keyboardShown = false;
+ }
}
private void cancelTimers() {
@@ -184,7 +202,8 @@ public class NoteEditFragment extends SearchableBaseNoteFragment {
*/
@Override
protected String getContent() {
- return binding.editContent.getText().toString();
+ final Editable editable = binding.editContent.getText();
+ return editable == null ? "" : editable.toString();
}
@Override
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/edit/NotePreviewFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/edit/NotePreviewFragment.java
index f1626149..f15d0e59 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/edit/NotePreviewFragment.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/edit/NotePreviewFragment.java
@@ -155,22 +155,22 @@ public class NotePreviewFragment extends SearchableBaseNoteFragment implements O
public void onRefresh() {
if (noteLoaded && repo.isSyncPossible() && SSOUtil.isConfigured(getContext())) {
binding.swiperefreshlayout.setRefreshing(true);
- new Thread(() -> {
+ executor.submit(() -> {
try {
final Account account = repo.getAccountByName(SingleAccountHelper.getCurrentSingleSignOnAccount(requireContext()).name);
- repo.addCallbackPull(account, () -> new Thread(() -> {
+ repo.addCallbackPull(account, () -> executor.submit(() -> {
note = repo.getNoteById(note.getId());
changedText = note.getContent();
requireActivity().runOnUiThread(() -> {
binding.singleNoteContent.setMarkdownString(note.getContent());
binding.swiperefreshlayout.setRefreshing(false);
});
- }).start());
+ }));
repo.scheduleSync(account, false);
} catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
e.printStackTrace();
}
- }).start();
+ });
} else {
binding.swiperefreshlayout.setRefreshing(false);
Toast.makeText(requireContext(), getString(R.string.error_sync, getString(R.string.error_no_network)), Toast.LENGTH_LONG).show();
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/importaccount/ImportAccountActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/importaccount/ImportAccountActivity.java
index c942b345..2e78af2c 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/importaccount/ImportAccountActivity.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/importaccount/ImportAccountActivity.java
@@ -9,6 +9,7 @@ import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;
+import androidx.preference.PreferenceManager;
import com.nextcloud.android.sso.AccountImporter;
import com.nextcloud.android.sso.exceptions.AccountImportCancelledException;
@@ -20,14 +21,17 @@ import com.nextcloud.android.sso.helper.SingleAccountHelper;
import com.nextcloud.android.sso.ui.UiExceptionManager;
import java.net.HttpURLConnection;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.branding.BrandingUtil;
import it.niedermann.owncloud.notes.databinding.ActivityImportAccountBinding;
import it.niedermann.owncloud.notes.exception.ExceptionDialogFragment;
import it.niedermann.owncloud.notes.exception.ExceptionHandler;
-import it.niedermann.owncloud.notes.persistence.CapabilitiesClient;
import it.niedermann.owncloud.notes.persistence.ApiProvider;
+import it.niedermann.owncloud.notes.persistence.CapabilitiesClient;
+import it.niedermann.owncloud.notes.persistence.SyncWorker;
import it.niedermann.owncloud.notes.persistence.entity.Account;
import it.niedermann.owncloud.notes.shared.model.Capabilities;
import it.niedermann.owncloud.notes.shared.model.IResponseCallback;
@@ -37,6 +41,8 @@ public class ImportAccountActivity extends AppCompatActivity {
private static final String TAG = ImportAccountActivity.class.getSimpleName();
public static final int REQUEST_CODE_IMPORT_ACCOUNT = 1;
+ private final ExecutorService executor = Executors.newSingleThreadExecutor();
+
private ImportAccountViewModel importAccountViewModel;
private ActivityImportAccountBinding binding;
@@ -84,12 +90,19 @@ public class ImportAccountActivity extends AppCompatActivity {
runOnUiThread(() -> binding.progressCircular.setVisibility(View.VISIBLE));
SingleAccountHelper.setCurrentAccount(getApplicationContext(), ssoAccount.name);
- new Thread(() -> {
+ executor.submit(() -> {
Log.i(TAG, "Added account: " + "name:" + ssoAccount.name + ", " + ssoAccount.url + ", userId" + ssoAccount.userId);
try {
Log.i(TAG, "Loading capabilities for " + ssoAccount.name);
- final Capabilities capabilities = CapabilitiesClient.getCapabilities(getApplicationContext(), ssoAccount, null);
- importAccountViewModel.addAccount(ssoAccount.url, ssoAccount.userId, ssoAccount.name, capabilities, new IResponseCallback<Account>() {
+ final Capabilities capabilities = CapabilitiesClient.getCapabilities(getApplicationContext(), ssoAccount, null, ApiProvider.getInstance());
+ final String displayName = CapabilitiesClient.getDisplayName(getApplicationContext(), ssoAccount, ApiProvider.getInstance());
+ importAccountViewModel.addAccount(ssoAccount.url, ssoAccount.userId, ssoAccount.name, capabilities, displayName, new IResponseCallback<Account>() {
+
+ /**
+ * Update syncing when adding account
+ * https://github.com/stefan-niedermann/nextcloud-deck/issues/531
+ * @param account the account to add
+ */
@Override
public void onSuccess(Account account) {
runOnUiThread(() -> {
@@ -98,6 +111,8 @@ public class ImportAccountActivity extends AppCompatActivity {
setResult(RESULT_OK);
finish();
});
+ SyncWorker.update(ImportAccountActivity.this, PreferenceManager.getDefaultSharedPreferences(ImportAccountActivity.this)
+ .getBoolean(getString(R.string.pref_key_background_sync), true));
}
@Override
@@ -110,7 +125,7 @@ public class ImportAccountActivity extends AppCompatActivity {
});
} catch (Throwable t) {
t.printStackTrace();
- ApiProvider.invalidateAPICache(ssoAccount);
+ ApiProvider.getInstance().invalidateAPICache(ssoAccount);
SingleAccountHelper.setCurrentAccount(this, null);
runOnUiThread(() -> {
restoreCleanState();
@@ -120,7 +135,7 @@ public class ImportAccountActivity extends AppCompatActivity {
} else if (t instanceof NetworkErrorException) {
binding.status.setText(getString(R.string.error_sync, getString(R.string.error_no_network)));
binding.status.setVisibility(View.VISIBLE);
- } else if (t instanceof UnknownErrorException && t.getMessage().contains("No address associated with hostname")) {
+ } else if (t instanceof UnknownErrorException && t.getMessage() != null && t.getMessage().contains("No address associated with hostname")) {
// https://github.com/stefan-niedermann/nextcloud-notes/issues/1014
binding.status.setText(R.string.you_have_to_be_connected_to_the_internet_in_order_to_add_an_account);
binding.status.setVisibility(View.VISIBLE);
@@ -129,7 +144,7 @@ public class ImportAccountActivity extends AppCompatActivity {
}
});
}
- }).start();
+ });
});
} catch (AccountImportCancelledException e) {
restoreCleanState();
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/importaccount/ImportAccountViewModel.java b/app/src/main/java/it/niedermann/owncloud/notes/importaccount/ImportAccountViewModel.java
index 905a59b1..70b3b565 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/importaccount/ImportAccountViewModel.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/importaccount/ImportAccountViewModel.java
@@ -3,6 +3,7 @@ package it.niedermann.owncloud.notes.importaccount;
import android.app.Application;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
@@ -23,7 +24,7 @@ public class ImportAccountViewModel extends AndroidViewModel {
this.repo = NotesRepository.getInstance(application);
}
- public void addAccount(@NonNull String url, @NonNull String username, @NonNull String accountName, @NonNull Capabilities capabilities, @NonNull IResponseCallback<Account> callback) {
- repo.addAccount(url, username, accountName, capabilities, callback);
+ public void addAccount(@NonNull String url, @NonNull String username, @NonNull String accountName, @NonNull Capabilities capabilities, @Nullable String displayName, @NonNull IResponseCallback<Account> callback) {
+ repo.addAccount(url, username, accountName, capabilities, displayName, callback);
}
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/main/MainActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/main/MainActivity.java
index d889689d..83bf8e0b 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/main/MainActivity.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/main/MainActivity.java
@@ -4,19 +4,18 @@ import android.accounts.NetworkErrorException;
import android.animation.AnimatorInflater;
import android.app.SearchManager;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
-import android.view.ViewTreeObserver;
-import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.view.ActionMode;
import androidx.appcompat.widget.SearchView;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
@@ -49,6 +48,11 @@ import com.nextcloud.android.sso.helper.SingleAccountHelper;
import java.net.HttpURLConnection;
import java.util.Collection;
import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
import it.niedermann.owncloud.notes.LockedActivity;
import it.niedermann.owncloud.notes.R;
@@ -73,9 +77,9 @@ import it.niedermann.owncloud.notes.main.menu.MenuAdapter;
import it.niedermann.owncloud.notes.main.navigation.NavigationAdapter;
import it.niedermann.owncloud.notes.main.navigation.NavigationClickListener;
import it.niedermann.owncloud.notes.main.navigation.NavigationItem;
+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.ApiProvider;
import it.niedermann.owncloud.notes.persistence.entity.Account;
import it.niedermann.owncloud.notes.persistence.entity.Note;
import it.niedermann.owncloud.notes.shared.model.Capabilities;
@@ -83,7 +87,9 @@ import it.niedermann.owncloud.notes.shared.model.CategorySortingMethod;
import it.niedermann.owncloud.notes.shared.model.IResponseCallback;
import it.niedermann.owncloud.notes.shared.model.NavigationCategory;
import it.niedermann.owncloud.notes.shared.model.NoteClickListener;
+import it.niedermann.owncloud.notes.shared.util.CustomAppGlideModule;
import it.niedermann.owncloud.notes.shared.util.NoteUtil;
+import it.niedermann.owncloud.notes.shared.util.ShareUtil;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES.O;
@@ -92,7 +98,6 @@ import static android.view.View.VISIBLE;
import static it.niedermann.owncloud.notes.NotesApplication.isDarkThemeActive;
import static it.niedermann.owncloud.notes.NotesApplication.isGridViewEnabled;
import static it.niedermann.owncloud.notes.branding.BrandingUtil.getSecondaryForegroundColorDependingOnTheme;
-import static it.niedermann.owncloud.notes.main.menu.MenuAdapter.SERVER_SETTINGS;
import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.DEFAULT_CATEGORY;
import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.FAVORITES;
import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.RECENT;
@@ -104,18 +109,19 @@ public class MainActivity extends LockedActivity implements NoteClickListener, A
private static final String TAG = MainActivity.class.getSimpleName();
+ protected final ExecutorService executor = Executors.newCachedThreadPool();
+
protected MainViewModel mainViewModel;
private CategoryViewModel categoryViewModel;
private boolean gridView = true;
- public static final String CREATED_NOTE = "it.niedermann.owncloud.notes.created_notes";
public static final String ADAPTER_KEY_RECENT = "recent";
public static final String ADAPTER_KEY_STARRED = "starred";
public static final String ADAPTER_KEY_UNCATEGORIZED = "uncategorized";
- private final static int create_note_cmd = 0;
- private final static int show_single_note_cmd = 1;
+ private static final int REQUEST_CODE_CREATE_NOTE = 0;
+ private static final int REQUEST_CODE_SERVER_SETTINGS = 1;
protected ItemAdapter adapter;
private NavigationAdapter adapterCategories;
@@ -165,14 +171,47 @@ public class MainActivity extends LockedActivity implements NoteClickListener, A
if (count == 0) {
startActivityForResult(new Intent(this, ImportAccountActivity.class), ImportAccountActivity.REQUEST_CODE_IMPORT_ACCOUNT);
} else {
- new Thread(() -> {
+ executor.submit(() -> {
try {
final Account account = mainViewModel.getLocalAccountByAccountName(SingleAccountHelper.getCurrentSingleSignOnAccount(getApplicationContext()).name);
runOnUiThread(() -> mainViewModel.postCurrentAccount(account));
- } catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
+ } catch (NextcloudFilesAppAccountNotFoundException e) {
+ // Verbose log output for https://github.com/stefan-niedermann/nextcloud-notes/issues/1256
+ runOnUiThread(() -> new AlertDialog.Builder(this)
+ .setTitle(NextcloudFilesAppAccountNotFoundException.class.getSimpleName())
+ .setMessage(R.string.backup)
+ .setPositiveButton(R.string.simple_backup, (a, b) -> executor.submit(() -> {
+ final List<Note> modifiedNotes = new LinkedList<>();
+ for (Account account : mainViewModel.getAccounts()) {
+ modifiedNotes.addAll(mainViewModel.getLocalModifiedNotes(account.getId()));
+ }
+ if (modifiedNotes.size() == 1) {
+ final Note note = modifiedNotes.get(0);
+ ShareUtil.openShareDialog(this, note.getTitle(), note.getContent());
+ } else {
+ ShareUtil.openShareDialog(this,
+ getResources().getQuantityString(R.plurals.share_multiple, modifiedNotes.size(), modifiedNotes.size()),
+ mainViewModel.collectNoteContents(modifiedNotes.stream().map(Note::getId).collect(Collectors.toList())));
+ }
+ }))
+ .setNegativeButton(R.string.simple_error, (a, b) -> {
+ final SharedPreferences ssoPreferences = AccountImporter.getSharedPreferences(getApplicationContext());
+ final StringBuilder ssoPreferencesString = new StringBuilder()
+ .append("Current SSO account: ").append(ssoPreferences.getString("PREF_CURRENT_ACCOUNT_STRING", null)).append("\n")
+ .append("\n")
+ .append("SSO SharedPreferences: ").append("\n");
+ for (Map.Entry<String, ?> entry : ssoPreferences.getAll().entrySet()) {
+ ssoPreferencesString.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
+ }
+ ssoPreferencesString.append("\n")
+ .append("Available accounts in DB: ").append(TextUtils.join(", ", mainViewModel.getAccounts().stream().map(Account::getAccountName).collect(Collectors.toList())));
+ runOnUiThread(() -> ExceptionDialogFragment.newInstance(new RuntimeException(e.getMessage(), new RuntimeException(ssoPreferencesString.toString(), e))).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()));
+ })
+ .show());
+ } catch (NoCurrentAccountSelectedException e) {
runOnUiThread(() -> ExceptionDialogFragment.newInstance(e).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()));
}
- }).start();
+ });
}
});
@@ -212,13 +251,13 @@ public class MainActivity extends LockedActivity implements NoteClickListener, A
}
fabCreate.setOnClickListener((View view) -> {
- Intent createIntent = new Intent(getApplicationContext(), EditNoteActivity.class);
+ final Intent createIntent = new Intent(getApplicationContext(), EditNoteActivity.class);
createIntent.putExtra(EditNoteActivity.PARAM_CATEGORY, selectedCategory);
if (activityBinding.searchView.getQuery().length() > 0) {
createIntent.putExtra(EditNoteActivity.PARAM_CONTENT, activityBinding.searchView.getQuery().toString());
invalidateOptionsMenu();
}
- startActivityForResult(createIntent, create_note_cmd);
+ startActivityForResult(createIntent, REQUEST_CODE_CREATE_NOTE);
});
});
mainViewModel.getNotesListLiveData().observe(this, notes -> {
@@ -298,18 +337,18 @@ public class MainActivity extends LockedActivity implements NoteClickListener, A
activityBinding.launchAccountSwitcher.setOnClickListener((v) -> AccountSwitcherDialog.newInstance(nextAccount.getId()).show(getSupportFragmentManager(), AccountSwitcherDialog.class.getSimpleName()));
if (menuAdapter == null) {
- menuAdapter = new MenuAdapter(getApplicationContext(), nextAccount, (menuItem) -> {
+ menuAdapter = new MenuAdapter(getApplicationContext(), nextAccount, REQUEST_CODE_SERVER_SETTINGS, (menuItem) -> {
@Nullable Integer resultCode = menuItem.getResultCode();
if (resultCode == null) {
startActivity(menuItem.getIntent());
} else {
- startActivityForResult(menuItem.getIntent(), menuItem.getResultCode());
+ startActivityForResult(menuItem.getIntent(), resultCode);
}
});
binding.navigationMenu.setAdapter(menuAdapter);
} else {
- menuAdapter.updateAccount(nextAccount);
+ menuAdapter.updateAccount(this, nextAccount);
}
});
}
@@ -353,36 +392,14 @@ public class MainActivity extends LockedActivity implements NoteClickListener, A
setSupportActionBar(binding.activityNotesListView.toolbar);
activityBinding.homeToolbar.setOnClickListener((v) -> {
if (activityBinding.toolbar.getVisibility() == GONE) {
- updateToolbars(false);
+ updateToolbars(true);
}
});
activityBinding.menuButton.setOnClickListener((v) -> binding.drawerLayout.openDrawer(GravityCompat.START));
-
- final LinearLayout searchEditFrame = activityBinding.searchView.findViewById(R.id.search_edit_frame);
-
- searchEditFrame.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
- int oldVisibility = -1;
-
- @Override
- public void onGlobalLayout() {
- int currentVisibility = searchEditFrame.getVisibility();
-
- if (currentVisibility != oldVisibility) {
- if (currentVisibility == VISIBLE) {
- fabCreate.hide();
- } else {
- new Handler().postDelayed(() -> fabCreate.show(), 150);
- }
-
- oldVisibility = currentVisibility;
- }
- }
-
- });
activityBinding.searchView.setOnCloseListener(() -> {
if (activityBinding.toolbar.getVisibility() == VISIBLE && TextUtils.isEmpty(activityBinding.searchView.getQuery())) {
- updateToolbars(true);
+ updateToolbars(false);
return true;
}
return false;
@@ -438,12 +455,7 @@ public class MainActivity extends LockedActivity implements NoteClickListener, A
});
swipeRefreshLayout.setOnRefreshListener(() -> {
- Log.i(TAG, "Clearing Glide memory cache");
- Glide.get(this).clearMemory();
- new Thread(() -> {
- Log.i(TAG, "Clearing Glide disk cache");
- Glide.get(getApplicationContext()).clearDiskCache();
- }, "CLEAR_GLIDE_CACHE").start();
+ CustomAppGlideModule.clearCache(this);
final LiveData<Account> syncLiveData = mainViewModel.getCurrentAccount();
final Observer<Account> syncObserver = currentAccount -> {
syncLiveData.removeObservers(this);
@@ -586,7 +598,7 @@ public class MainActivity extends LockedActivity implements NoteClickListener, A
@Override
public boolean onSupportNavigateUp() {
if (activityBinding.toolbar.getVisibility() == VISIBLE) {
- updateToolbars(true);
+ updateToolbars(false);
return true;
} else {
return super.onSupportNavigateUp();
@@ -638,33 +650,37 @@ public class MainActivity extends LockedActivity implements NoteClickListener, A
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
- case create_note_cmd: {
+ case REQUEST_CODE_CREATE_NOTE: {
listView.scrollToPosition(0);
break;
}
- case SERVER_SETTINGS: {
+ case REQUEST_CODE_SERVER_SETTINGS: {
// Recreate activity completely, because theme switching makes problems when only invalidating the views.
// @see https://github.com/stefan-niedermann/nextcloud-notes/issues/529
- ActivityCompat.recreate(this);
+ if (RESULT_OK == resultCode) {
+ ActivityCompat.recreate(this);
+ return;
+ }
break;
}
default: {
try {
AccountImporter.onActivityResult(requestCode, resultCode, data, this, (ssoAccount) -> {
CapabilitiesWorker.update(this);
- new Thread(() -> {
+ executor.submit(() -> {
Log.i(TAG, "Added account: " + "name:" + ssoAccount.name + ", " + ssoAccount.url + ", userId" + ssoAccount.userId);
try {
Log.i(TAG, "Refreshing capabilities for " + ssoAccount.name);
- final Capabilities capabilities = CapabilitiesClient.getCapabilities(getApplicationContext(), ssoAccount, null);
- mainViewModel.addAccount(ssoAccount.url, ssoAccount.userId, ssoAccount.name, capabilities, new IResponseCallback<Account>() {
+ final Capabilities capabilities = CapabilitiesClient.getCapabilities(getApplicationContext(), ssoAccount, null, ApiProvider.getInstance());
+ final String displayName = CapabilitiesClient.getDisplayName(getApplicationContext(), ssoAccount, ApiProvider.getInstance());
+ mainViewModel.addAccount(ssoAccount.url, ssoAccount.userId, ssoAccount.name, capabilities, displayName, new IResponseCallback<Account>() {
@Override
public void onSuccess(Account result) {
- new Thread(() -> {
+ executor.submit(() -> {
Log.i(TAG, capabilities.toString());
final Account a = mainViewModel.getLocalAccountByAccountName(ssoAccount.name);
runOnUiThread(() -> mainViewModel.postCurrentAccount(a));
- }).start();
+ });
}
@Override
@@ -673,7 +689,7 @@ public class MainActivity extends LockedActivity implements NoteClickListener, A
}
});
} catch (Throwable e) {
- ApiProvider.invalidateAPICache(ssoAccount);
+ ApiProvider.getInstance().invalidateAPICache(ssoAccount);
// Happens when importing an already existing account the second time
if (e instanceof TokenMismatchException && mainViewModel.getLocalAccountByAccountName(ssoAccount.name) != null) {
Log.w(TAG, "Received " + TokenMismatchException.class.getSimpleName() + " and the given ssoAccount.name (" + ssoAccount.name + ") does already exist in the database. Assume that this account has already been imported.");
@@ -682,7 +698,7 @@ public class MainActivity extends LockedActivity implements NoteClickListener, A
// TODO there is already a sync in progress and results in displaying a TokenMissMatchException snackbar which conflicts with this one
coordinatorLayout.post(() -> BrandedSnackbar.make(coordinatorLayout, R.string.account_already_imported, Snackbar.LENGTH_LONG).show());
});
- } else if (e instanceof UnknownErrorException && e.getMessage().contains("No address associated with hostname")) {
+ } else if (e instanceof UnknownErrorException && e.getMessage() != null && e.getMessage().contains("No address associated with hostname")) {
// https://github.com/stefan-niedermann/nextcloud-notes/issues/1014
runOnUiThread(() -> Snackbar.make(coordinatorLayout, R.string.you_have_to_be_connected_to_the_internet_in_order_to_add_an_account, Snackbar.LENGTH_LONG).show());
} else {
@@ -693,7 +709,7 @@ public class MainActivity extends LockedActivity implements NoteClickListener, A
});
}
}
- }).start();
+ });
});
} catch (AccountImportCancelledException e) {
Log.i(TAG, "AccountImport has been cancelled.");
@@ -706,10 +722,9 @@ public class MainActivity extends LockedActivity implements NoteClickListener, A
public void onNoteClick(int position, View v) {
boolean hasCheckedItems = tracker.getSelection().size() > 0;
if (!hasCheckedItems) {
- Note note = (Note) adapter.getItem(position);
- Intent intent = new Intent(getApplicationContext(), EditNoteActivity.class);
- intent.putExtra(EditNoteActivity.PARAM_NOTE_ID, note.getId());
- startActivityForResult(intent, show_single_note_cmd);
+ final Note note = (Note) adapter.getItem(position);
+ startActivity(new Intent(getApplicationContext(), EditNoteActivity.class)
+ .putExtra(EditNoteActivity.PARAM_NOTE_ID, note.getId()));
}
}
@@ -722,18 +737,24 @@ public class MainActivity extends LockedActivity implements NoteClickListener, A
@Override
public void onBackPressed() {
if (activityBinding.toolbar.getVisibility() == VISIBLE) {
- updateToolbars(true);
+ updateToolbars(false);
+ } else if (binding.drawerLayout.isDrawerOpen(GravityCompat.START)) {
+ binding.drawerLayout.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}
- private void updateToolbars(boolean disableSearch) {
- activityBinding.homeToolbar.setVisibility(disableSearch ? VISIBLE : GONE);
- activityBinding.toolbar.setVisibility(disableSearch ? GONE : VISIBLE);
- activityBinding.appBar.setStateListAnimator(AnimatorInflater.loadStateListAnimator(activityBinding.appBar.getContext(),
- disableSearch ? R.animator.appbar_elevation_off : R.animator.appbar_elevation_on));
- if (disableSearch) {
+ private void updateToolbars(boolean enableSearch) {
+ activityBinding.homeToolbar.setVisibility(enableSearch ? GONE : VISIBLE);
+ activityBinding.toolbar.setVisibility(enableSearch ? VISIBLE : GONE);
+ activityBinding.appBar.setStateListAnimator(AnimatorInflater.loadStateListAnimator(activityBinding.appBar.getContext(), enableSearch
+ ? R.animator.appbar_elevation_on
+ : R.animator.appbar_elevation_off));
+ if (enableSearch) {
+ activityBinding.searchView.setIconified(false);
+ fabCreate.show();
+ } else {
activityBinding.searchView.setQuery(null, true);
}
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/main/MainViewModel.java b/app/src/main/java/it/niedermann/owncloud/notes/main/MainViewModel.java
index ec0e71c7..92790bc4 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/main/MainViewModel.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/main/MainViewModel.java
@@ -20,18 +20,23 @@ import com.nextcloud.android.sso.AccountImporter;
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException;
import com.nextcloud.android.sso.helper.SingleAccountHelper;
+import com.nextcloud.android.sso.model.SingleSignOnAccount;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.stream.Collectors;
+import it.niedermann.owncloud.notes.BuildConfig;
import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.branding.BrandingUtil;
import it.niedermann.owncloud.notes.exception.IntendedOfflineException;
import it.niedermann.owncloud.notes.main.navigation.NavigationAdapter;
import it.niedermann.owncloud.notes.main.navigation.NavigationItem;
+import it.niedermann.owncloud.notes.persistence.ApiProvider;
import it.niedermann.owncloud.notes.persistence.CapabilitiesClient;
import it.niedermann.owncloud.notes.persistence.NotesRepository;
import it.niedermann.owncloud.notes.persistence.entity.Account;
@@ -64,6 +69,8 @@ public class MainViewModel extends AndroidViewModel {
private static final String TAG = MainViewModel.class.getSimpleName();
+ private final ExecutorService executor = Executors.newCachedThreadPool();
+
private final SavedStateHandle state;
private static final String KEY_CURRENT_ACCOUNT = "currentAccount";
@@ -214,7 +221,7 @@ public class MainViewModel extends AndroidViewModel {
} else {
Log.v(TAG, "[getNotesListLiveData] - selectedCategory: " + selectedCategory);
return switchMap(getSearchTerm(), searchTerm -> {
- Log.v(TAG, "[getNotesListLiveData] - searchTerm: " + searchTerm);
+ Log.v(TAG, "[getNotesListLiveData] - searchTerm: " + (BuildConfig.DEBUG ? "******" : searchTerm));
return switchMap(getCategorySortingMethodOfSelectedCategory(), sortingMethod -> {
final long accountId = currentAccount.getId();
final String searchQueryOrWildcard = searchTerm == null ? "%" : "%" + searchTerm.trim() + "%";
@@ -388,32 +395,35 @@ public class MainViewModel extends AndroidViewModel {
* Updates the network status if necessary and pulls the latest {@link Capabilities} of the given {@param localAccount}
*/
public void synchronizeCapabilities(@NonNull Account localAccount, @NonNull IResponseCallback<Void> callback) {
- new Thread(() -> {
+ executor.submit(() -> {
if (!repo.isSyncPossible()) {
repo.updateNetworkStatus();
}
if (repo.isSyncPossible()) {
try {
- final Capabilities capabilities = CapabilitiesClient.getCapabilities(getApplication(), AccountImporter.getSingleSignOnAccount(getApplication(), localAccount.getAccountName()), localAccount.getCapabilitiesETag());
- repo.updateCapabilitiesETag(localAccount.getId(), capabilities.getETag());
- repo.updateBrand(localAccount.getId(), capabilities.getColor(), capabilities.getTextColor());
- localAccount.setColor(capabilities.getColor());
- localAccount.setTextColor(capabilities.getTextColor());
- BrandingUtil.saveBrandColors(getApplication(), localAccount.getColor(), localAccount.getTextColor());
- repo.updateApiVersion(localAccount.getId(), capabilities.getApiVersion());
- callback.onSuccess(null);
+ final SingleSignOnAccount ssoAccount = AccountImporter.getSingleSignOnAccount(getApplication(), localAccount.getAccountName());
+ try {
+ final Capabilities capabilities = CapabilitiesClient.getCapabilities(getApplication(), ssoAccount, localAccount.getCapabilitiesETag(), ApiProvider.getInstance());
+ repo.updateCapabilitiesETag(localAccount.getId(), capabilities.getETag());
+ repo.updateBrand(localAccount.getId(), capabilities.getColor(), capabilities.getTextColor());
+ localAccount.setColor(capabilities.getColor());
+ localAccount.setTextColor(capabilities.getTextColor());
+ BrandingUtil.saveBrandColors(getApplication(), localAccount.getColor(), localAccount.getTextColor());
+ repo.updateApiVersion(localAccount.getId(), capabilities.getApiVersion());
+ callback.onSuccess(null);
+ } catch (Throwable t) {
+ if (t.getClass() == NextcloudHttpRequestFailedException.class || t instanceof NextcloudHttpRequestFailedException) {
+ if (((NextcloudHttpRequestFailedException) t).getStatusCode() == HTTP_NOT_MODIFIED) {
+ Log.d(TAG, "Server returned HTTP Status Code " + ((NextcloudHttpRequestFailedException) t).getStatusCode() + " - Capabilities not modified.");
+ callback.onSuccess(null);
+ return;
+ }
+ }
+ callback.onError(t);
+ }
} catch (NextcloudFilesAppAccountNotFoundException e) {
repo.deleteAccount(localAccount);
callback.onError(e);
- } catch (Throwable t) {
- if (t.getClass() == NextcloudHttpRequestFailedException.class || t instanceof NextcloudHttpRequestFailedException) {
- if (((NextcloudHttpRequestFailedException) t).getStatusCode() == HTTP_NOT_MODIFIED) {
- Log.d(TAG, "Server returned HTTP Status Code " + ((NextcloudHttpRequestFailedException) t).getStatusCode() + " - Capabilities not modified.");
- callback.onSuccess(null);
- return;
- }
- }
- callback.onError(t);
}
} else {
if (repo.isNetworkConnected() && repo.isSyncOnlyOnWifi()) {
@@ -422,14 +432,14 @@ public class MainViewModel extends AndroidViewModel {
callback.onError(new NetworkErrorException("Sync is not possible, because network is not connected."));
}
}
- }, "SYNC_CAPABILITIES").start();
+ }, "SYNC_CAPABILITIES");
}
/**
* Updates the network status if necessary and pulls the latest notes of the given {@param localAccount}
*/
public void synchronizeNotes(@NonNull Account currentAccount, @NonNull IResponseCallback<Void> callback) {
- new Thread(() -> {
+ executor.submit(() -> {
Log.v(TAG, "[synchronize] - currentAccount: " + currentAccount.getAccountName());
if (!repo.isSyncPossible()) {
repo.updateNetworkStatus();
@@ -444,7 +454,7 @@ public class MainViewModel extends AndroidViewModel {
callback.onError(new NetworkErrorException("Sync is not possible, because network is not connected."));
}
}
- }, "SYNC_NOTES").start();
+ }, "SYNC_NOTES");
}
public LiveData<Boolean> getSyncStatus() {
@@ -483,9 +493,9 @@ public class MainViewModel extends AndroidViewModel {
});
}
- public LiveData<Note> moveNoteToAnotherAccount(Account account, Long noteId) {
+ public LiveData<Note> moveNoteToAnotherAccount(Account account, long noteId) {
return switchMap(repo.getNoteById$(noteId), (note) -> {
- Log.v(TAG, "[moveNoteToAnotherAccount] - note: " + note);
+ Log.v(TAG, "[moveNoteToAnotherAccount] - note: " + (BuildConfig.DEBUG ? note : note.getTitle()));
return repo.moveNoteToAnotherAccount(account, note);
});
}
@@ -528,8 +538,8 @@ public class MainViewModel extends AndroidViewModel {
});
}
- public void addAccount(@NonNull String url, @NonNull String username, @NonNull String accountName, @NonNull Capabilities capabilities, @NonNull IResponseCallback<Account> callback) {
- repo.addAccount(url, username, accountName, capabilities, callback);
+ public void addAccount(@NonNull String url, @NonNull String username, @NonNull String accountName, @NonNull Capabilities capabilities, @Nullable String displayName, @NonNull IResponseCallback<Account> callback) {
+ repo.addAccount(url, username, accountName, capabilities, displayName, callback);
}
public LiveData<Note> getFullNote$(long id) {
@@ -548,12 +558,12 @@ public class MainViewModel extends AndroidViewModel {
} else {
Log.v(TAG, "[getNote] - currentAccount: " + currentAccount.getAccountName());
final MutableLiveData<List<Note>> notes = new MutableLiveData<>();
- new Thread(() -> notes.postValue(
+ executor.submit(() -> notes.postValue(
ids
.stream()
.map(repo::getNoteById)
.collect(Collectors.toList())
- )).start();
+ ));
return notes;
}
});
@@ -584,6 +594,10 @@ public class MainViewModel extends AndroidViewModel {
repo.createOrUpdateSingleNoteWidgetData(data);
}
+ public List<Note> getLocalModifiedNotes(long accountId) {
+ return repo.getLocalModifiedNotes(accountId);
+ }
+
public LiveData<Integer> getAccountsCount() {
return repo.countAccounts$();
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/main/MultiSelectedActionModeCallback.java b/app/src/main/java/it/niedermann/owncloud/notes/main/MultiSelectedActionModeCallback.java
index fa9bb879..cb51cdfc 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/main/MultiSelectedActionModeCallback.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/main/MultiSelectedActionModeCallback.java
@@ -21,6 +21,8 @@ import com.google.android.material.snackbar.Snackbar;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.accountpicker.AccountPickerDialogFragment;
@@ -32,6 +34,7 @@ import it.niedermann.owncloud.notes.shared.util.ShareUtil;
public class MultiSelectedActionModeCallback implements Callback {
+ private final ExecutorService executor = Executors.newSingleThreadExecutor();
@ColorInt
private final int colorAccent;
@NonNull
@@ -125,11 +128,9 @@ public class MultiSelectedActionModeCallback implements Callback {
final LiveData<Account> currentAccount$ = mainViewModel.getCurrentAccount();
currentAccount$.observe(lifecycleOwner, account -> {
currentAccount$.removeObservers(lifecycleOwner);
- new Thread(() -> {
- AccountPickerDialogFragment
- .newInstance(new ArrayList<>(mainViewModel.getAccounts()), account.getId())
- .show(fragmentManager, AccountPickerDialogFragment.class.getSimpleName());
- }).start();
+ executor.submit(() -> AccountPickerDialogFragment
+ .newInstance(new ArrayList<>(mainViewModel.getAccounts()), account.getId())
+ .show(fragmentManager, AccountPickerDialogFragment.class.getSimpleName()));
});
return true;
} else if (itemId == R.id.menu_share) {
@@ -139,7 +140,7 @@ public class MultiSelectedActionModeCallback implements Callback {
}
tracker.clearSelection();
- new Thread(() -> {
+ executor.submit(() -> {
if (selection.size() == 1) {
final Note note = mainViewModel.getFullNote(selection.get(0));
ShareUtil.openShareDialog(context, note.getTitle(), note.getContent());
@@ -148,7 +149,7 @@ public class MultiSelectedActionModeCallback implements Callback {
context.getResources().getQuantityString(R.plurals.share_multiple, selection.size(), selection.size()),
mainViewModel.collectNoteContents(selection));
}
- }).start();
+ });
return true;
} else if (itemId == R.id.menu_category) {// TODO detect whether all selected notes do have the same category - in this case preselect it
final LiveData<Account> accountLiveData = mainViewModel.getCurrentAccount();
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/main/items/grid/GridItemDecoration.java b/app/src/main/java/it/niedermann/owncloud/notes/main/items/grid/GridItemDecoration.java
index 2cfcee88..01743638 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/main/items/grid/GridItemDecoration.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/main/items/grid/GridItemDecoration.java
@@ -20,6 +20,9 @@ public class GridItemDecoration extends SectionItemDecoration {
public GridItemDecoration(@NonNull ItemAdapter adapter, int spanCount, @Px int sectionLeft, @Px int sectionTop, @Px int sectionRight, @Px int sectionBottom, @Px int gutter) {
super(adapter, sectionLeft, sectionTop, sectionRight, sectionBottom);
+ if(spanCount < 1) {
+ throw new IllegalArgumentException("Requires at least one span");
+ }
this.spanCount = spanCount;
this.adapter = adapter;
this.gutter = gutter;
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/main/menu/MenuAdapter.java b/app/src/main/java/it/niedermann/owncloud/notes/main/menu/MenuAdapter.java
index 82d88d11..24c1a44e 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/main/menu/MenuAdapter.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/main/menu/MenuAdapter.java
@@ -2,15 +2,18 @@ package it.niedermann.owncloud.notes.main.menu;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.net.Uri;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
-import androidx.core.content.ContextCompat;
import androidx.core.util.Consumer;
import androidx.recyclerview.widget.RecyclerView;
+import com.nextcloud.android.sso.Constants;
+import com.nextcloud.android.sso.helper.VersionCheckHelper;
+
import it.niedermann.owncloud.notes.FormattingHelpActivity;
import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.about.AboutActivity;
@@ -20,21 +23,16 @@ import it.niedermann.owncloud.notes.preferences.PreferencesActivity;
public class MenuAdapter extends RecyclerView.Adapter<MenuViewHolder> {
- public static final int SERVER_SETTINGS = 2;
-
@NonNull
private final MenuItem[] menuItems;
@NonNull
private final Consumer<MenuItem> onClick;
- @NonNull
- private final Context context;
- public MenuAdapter(@NonNull Context context, @NonNull Account account, @NonNull Consumer<MenuItem> onClick) {
- this.context = context;
+ public MenuAdapter(@NonNull Context context, @NonNull Account account, int settingsRequestCode, @NonNull Consumer<MenuItem> onClick) {
this.menuItems = new MenuItem[]{
new MenuItem(new Intent(context, FormattingHelpActivity.class), R.string.action_formatting_help, R.drawable.ic_baseline_help_outline_24),
- new MenuItem(generateTrashbinIntent(account), R.string.action_trashbin, R.drawable.ic_delete_grey600_24dp),
- new MenuItem(new Intent(context, PreferencesActivity.class), SERVER_SETTINGS, R.string.action_settings, R.drawable.ic_settings_grey600_24dp),
+ new MenuItem(generateTrashbinIntent(context, account), R.string.action_trashbin, R.drawable.ic_delete_grey600_24dp),
+ new MenuItem(new Intent(context, PreferencesActivity.class), settingsRequestCode, R.string.action_settings, R.drawable.ic_settings_grey600_24dp),
new MenuItem(new Intent(context, AboutActivity.class), R.string.simple_about, R.drawable.ic_info_outline_grey600_24dp)
};
this.onClick = onClick;
@@ -54,11 +52,11 @@ public class MenuAdapter extends RecyclerView.Adapter<MenuViewHolder> {
@Override
public void onBindViewHolder(@NonNull MenuViewHolder holder, int position) {
- holder.bind(menuItems[position], onClick, ContextCompat.getColor(context, R.color.fg_default));
+ holder.bind(menuItems[position], onClick);
}
- public void updateAccount(@NonNull Account account) {
- menuItems[1].setIntent(new Intent(generateTrashbinIntent(account)));
+ public void updateAccount(@NonNull Context context, @NonNull Account account) {
+ menuItems[1].setIntent(new Intent(generateTrashbinIntent(context, account)));
}
@Override
@@ -67,7 +65,38 @@ public class MenuAdapter extends RecyclerView.Adapter<MenuViewHolder> {
}
@NonNull
- private static Intent generateTrashbinIntent(@NonNull Account account) {
+ private static Intent generateTrashbinIntent(@NonNull Context context, @NonNull Account account) {
+ // https://github.com/nextcloud/android/pull/8405#issuecomment-852966877
+ final int minVersionCode = 30170090;
+ try {
+ if (VersionCheckHelper.getNextcloudFilesVersionCode(context, true) > minVersionCode) {
+ return generateTrashbinAppIntent(context, account, true);
+ } else if (VersionCheckHelper.getNextcloudFilesVersionCode(context, false) > minVersionCode) {
+ return generateTrashbinAppIntent(context, account, false);
+ } else {
+ // Files app is too old to be able to switch the account when launching the TrashbinActivity
+ return generateTrashbinWebIntent(account);
+ }
+ } catch (PackageManager.NameNotFoundException | SecurityException e) {
+ e.printStackTrace();
+ return generateTrashbinWebIntent(account);
+ }
+ }
+
+ private static Intent generateTrashbinAppIntent(@NonNull Context context, @NonNull Account account, boolean prod) throws PackageManager.NameNotFoundException {
+ final PackageManager packageManager = context.getPackageManager();
+ final String packageName = prod ? Constants.PACKAGE_NAME_PROD : Constants.PACKAGE_NAME_DEV;
+ final Intent intent = new Intent();
+ intent.setClassName(packageName, "com.owncloud.android.ui.trashbin.TrashbinActivity");
+ if (packageManager.resolveActivity(intent, 0) != null) {
+ return intent
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .putExtra(Intent.EXTRA_USER, account.getAccountName());
+ }
+ throw new PackageManager.NameNotFoundException("Could not resolve target activity.");
+ }
+
+ private static Intent generateTrashbinWebIntent(@NonNull Account account) {
return new Intent(Intent.ACTION_VIEW, Uri.parse(account.getUrl() + "/index.php/apps/files/?dir=/&view=trashbin"));
}
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/main/menu/MenuViewHolder.java b/app/src/main/java/it/niedermann/owncloud/notes/main/menu/MenuViewHolder.java
index 0e08e279..d5a2f608 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/main/menu/MenuViewHolder.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/main/menu/MenuViewHolder.java
@@ -2,12 +2,12 @@ package it.niedermann.owncloud.notes.main.menu;
import android.content.Context;
-import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.core.util.Consumer;
import androidx.recyclerview.widget.RecyclerView;
+import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.databinding.ItemNavigationBinding;
import static android.view.View.GONE;
@@ -21,10 +21,10 @@ public class MenuViewHolder extends RecyclerView.ViewHolder {
this.binding = binding;
}
- public void bind(@NonNull MenuItem menuItem, @NonNull Consumer<MenuItem> onClick, @ColorInt int textColor) {
+ public void bind(@NonNull MenuItem menuItem, @NonNull Consumer<MenuItem> onClick) {
@NonNull Context context = itemView.getContext();
binding.navigationItemLabel.setText(context.getString(menuItem.getLabelResource()));
- binding.navigationItemLabel.setTextColor(textColor);
+ binding.navigationItemLabel.setTextColor(binding.getRoot().getResources().getColor(R.color.fg_default));
binding.navigationItemIcon.setImageDrawable(ContextCompat.getDrawable(context, menuItem.getDrawableResource()));
binding.navigationItemCount.setVisibility(GONE);
binding.getRoot().setOnClickListener((v) -> onClick.accept(menuItem));
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/manageaccounts/ManageAccountsViewModel.java b/app/src/main/java/it/niedermann/owncloud/notes/manageaccounts/ManageAccountsViewModel.java
index 2ee45cf8..ce88f2c2 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/manageaccounts/ManageAccountsViewModel.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/manageaccounts/ManageAccountsViewModel.java
@@ -13,8 +13,9 @@ import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException;
import com.nextcloud.android.sso.helper.SingleAccountHelper;
import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
-import it.niedermann.owncloud.notes.persistence.NotesDatabase;
import it.niedermann.owncloud.notes.persistence.NotesRepository;
import it.niedermann.owncloud.notes.persistence.entity.Account;
import it.niedermann.owncloud.notes.shared.model.IResponseCallback;
@@ -23,7 +24,7 @@ import static androidx.lifecycle.Transformations.distinctUntilChanged;
public class ManageAccountsViewModel extends AndroidViewModel {
- private static final String TAG = ManageAccountsViewModel.class.getSimpleName();
+ private final ExecutorService executor = Executors.newCachedThreadPool();
@NonNull
private final NotesRepository repo;
@@ -46,7 +47,7 @@ public class ManageAccountsViewModel extends AndroidViewModel {
}
public void deleteAccount(@NonNull Account account, @NonNull Context context) {
- new Thread(() -> {
+ executor.submit(() -> {
final List<Account> accounts = repo.getAccounts();
for (int i = 0; i < accounts.size(); i++) {
if (accounts.get(i).getId() == account.getId()) {
@@ -61,7 +62,7 @@ public class ManageAccountsViewModel extends AndroidViewModel {
break;
}
}
- }).start();
+ });
}
public void selectAccount(@Nullable Account account, @NonNull Context context) {
@@ -69,6 +70,6 @@ public class ManageAccountsViewModel extends AndroidViewModel {
}
public void countUnsynchronizedNotes(long accountId, @NonNull IResponseCallback<Long> callback) {
- new Thread(() -> callback.onSuccess(repo.countUnsynchronizedNotes(accountId))).start();
+ executor.submit(() -> callback.onSuccess(repo.countUnsynchronizedNotes(accountId)));
}
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/ApiProvider.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/ApiProvider.java
index b1bca215..6b851ddb 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/ApiProvider.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/ApiProvider.java
@@ -16,8 +16,8 @@ import com.nextcloud.android.sso.api.NextcloudAPI;
import com.nextcloud.android.sso.model.SingleSignOnAccount;
import java.util.Calendar;
-import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
import it.niedermann.owncloud.notes.persistence.sync.CapabilitiesDeserializer;
import it.niedermann.owncloud.notes.persistence.sync.NotesAPI;
@@ -36,17 +36,27 @@ public class ApiProvider {
private static final String TAG = ApiProvider.class.getSimpleName();
+ private static final ApiProvider INSTANCE = new ApiProvider();
+
private static final String API_ENDPOINT_OCS = "/ocs/v2.php/cloud/";
- private static final Map<String, NextcloudAPI> API_CACHE = new HashMap<>();
+ private static final Map<String, NextcloudAPI> API_CACHE = new ConcurrentHashMap<>();
+
+ private static final Map<String, OcsAPI> API_CACHE_OCS = new ConcurrentHashMap<>();
+ private static final Map<String, NotesAPI> API_CACHE_NOTES = new ConcurrentHashMap<>();
- private static final Map<String, OcsAPI> API_CACHE_OCS = new HashMap<>();
- private static final Map<String, NotesAPI> API_CACHE_NOTES = new HashMap<>();
+ public static ApiProvider getInstance() {
+ return INSTANCE;
+ }
+
+ private ApiProvider() {
+ // Singleton
+ }
/**
* An {@link OcsAPI} currently shares the {@link Gson} configuration with the {@link NotesAPI} and therefore divides all {@link Calendar} milliseconds by 1000 while serializing and multiplies values by 1000 during deserialization.
*/
- public static synchronized OcsAPI getOcsAPI(@NonNull Context context, @NonNull SingleSignOnAccount ssoAccount) {
+ public synchronized OcsAPI getOcsAPI(@NonNull Context context, @NonNull SingleSignOnAccount ssoAccount) {
if (API_CACHE_OCS.containsKey(ssoAccount.name)) {
return API_CACHE_OCS.get(ssoAccount.name);
}
@@ -58,7 +68,7 @@ public class ApiProvider {
/**
* In case the {@param preferredApiVersion} changes, call {@link #invalidateAPICache(SingleSignOnAccount)} or {@link #invalidateAPICache()} to make sure that this call returns a {@link NotesAPI} that uses the correct compatibility layer.
*/
- public static synchronized NotesAPI getNotesAPI(@NonNull Context context, @NonNull SingleSignOnAccount ssoAccount, @Nullable ApiVersion preferredApiVersion) {
+ public synchronized NotesAPI getNotesAPI(@NonNull Context context, @NonNull SingleSignOnAccount ssoAccount, @Nullable ApiVersion preferredApiVersion) {
if (API_CACHE_NOTES.containsKey(ssoAccount.name)) {
return API_CACHE_NOTES.get(ssoAccount.name);
}
@@ -67,7 +77,7 @@ public class ApiProvider {
return notesAPI;
}
- private static synchronized NextcloudAPI getNextcloudAPI(@NonNull Context context, @NonNull SingleSignOnAccount ssoAccount) {
+ private synchronized NextcloudAPI getNextcloudAPI(@NonNull Context context, @NonNull SingleSignOnAccount ssoAccount) {
if (API_CACHE.containsKey(ssoAccount.name)) {
return API_CACHE.get(ssoAccount.name);
} else {
@@ -104,7 +114,7 @@ public class ApiProvider {
*
* @param ssoAccount the ssoAccount for which the API cache should be cleared.
*/
- public static synchronized void invalidateAPICache(@NonNull SingleSignOnAccount ssoAccount) {
+ public synchronized void invalidateAPICache(@NonNull SingleSignOnAccount ssoAccount) {
Log.v(TAG, "Invalidating API cache for " + ssoAccount.name);
if (API_CACHE.containsKey(ssoAccount.name)) {
final NextcloudAPI nextcloudAPI = API_CACHE.get(ssoAccount.name);
@@ -120,7 +130,7 @@ public class ApiProvider {
/**
* Invalidates the whole API cache for all accounts
*/
- public static synchronized void invalidateAPICache() {
+ public synchronized void invalidateAPICache() {
for (String key : API_CACHE.keySet()) {
Log.v(TAG, "Invalidating API cache for " + key);
if (API_CACHE.containsKey(key)) {
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/CapabilitiesClient.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/CapabilitiesClient.java
index 8afc64b8..33e42382 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/CapabilitiesClient.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/CapabilitiesClient.java
@@ -14,6 +14,9 @@ import java.util.Map;
import it.niedermann.owncloud.notes.persistence.sync.OcsAPI;
import it.niedermann.owncloud.notes.shared.model.Capabilities;
+import it.niedermann.owncloud.notes.shared.model.OcsResponse;
+import it.niedermann.owncloud.notes.shared.model.OcsUser;
+import retrofit2.Response;
@WorkerThread
public class CapabilitiesClient {
@@ -22,11 +25,12 @@ public class CapabilitiesClient {
private static final String HEADER_KEY_ETAG = "ETag";
- public static Capabilities getCapabilities(@NonNull Context context, @NonNull SingleSignOnAccount ssoAccount, @Nullable String lastETag) throws Throwable {
- final OcsAPI ocsAPI = ApiProvider.getOcsAPI(context, ssoAccount);
+ @WorkerThread
+ public static Capabilities getCapabilities(@NonNull Context context, @NonNull SingleSignOnAccount ssoAccount, @Nullable String lastETag, @NonNull ApiProvider apiProvider) throws Throwable {
+ final OcsAPI ocsAPI = apiProvider.getOcsAPI(context, ssoAccount);
try {
- final ParsedResponse<Capabilities> response = ocsAPI.getCapabilities(lastETag).blockingSingle();
- final Capabilities capabilities = response.getResponse();
+ final ParsedResponse<OcsResponse<Capabilities>> response = ocsAPI.getCapabilities(lastETag).blockingSingle();
+ final Capabilities capabilities = response.getResponse().ocs.data;
final Map<String, String> headers = response.getHeaders();
if (headers != null) {
capabilities.setETag(headers.get(HEADER_KEY_ETAG));
@@ -36,11 +40,33 @@ public class CapabilitiesClient {
return capabilities;
} catch (RuntimeException e) {
final Throwable cause = e.getCause();
- if(cause != null) {
+ if (cause != null) {
throw cause;
} else {
throw e;
}
}
}
+
+ @WorkerThread
+ @Nullable
+ public static String getDisplayName(@NonNull Context context, @NonNull SingleSignOnAccount ssoAccount, @NonNull ApiProvider apiProvider) {
+ final OcsAPI ocsAPI = apiProvider.getOcsAPI(context, ssoAccount);
+ try {
+ final Response<OcsResponse<OcsUser>> userResponse = ocsAPI.getUser(ssoAccount.userId).execute();
+ if (userResponse.isSuccessful()) {
+ final OcsResponse<OcsUser> ocsResponse = userResponse.body();
+ if (ocsResponse != null) {
+ return ocsResponse.ocs.data.displayName;
+ } else {
+ Log.w(TAG, "ocsResponse is null");
+ }
+ } else {
+ Log.w(TAG, "Fetching user was not successful.");
+ }
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ return null;
+ }
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/CapabilitiesWorker.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/CapabilitiesWorker.java
index 1dff46cf..4f8852e7 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/CapabilitiesWorker.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/CapabilitiesWorker.java
@@ -47,17 +47,18 @@ public class CapabilitiesWorker extends Worker {
try {
final SingleSignOnAccount ssoAccount = AccountImporter.getSingleSignOnAccount(getApplicationContext(), account.getAccountName());
Log.i(TAG, "Refreshing capabilities for " + ssoAccount.name);
- final Capabilities capabilities = CapabilitiesClient.getCapabilities(getApplicationContext(), ssoAccount, account.getCapabilitiesETag());
+ final Capabilities capabilities = CapabilitiesClient.getCapabilities(getApplicationContext(), ssoAccount, account.getCapabilitiesETag(), ApiProvider.getInstance());
repo.updateCapabilitiesETag(account.getId(), capabilities.getETag());
repo.updateBrand(account.getId(), capabilities.getColor(), capabilities.getTextColor());
repo.updateApiVersion(account.getId(), capabilities.getApiVersion());
Log.i(TAG, capabilities.toString());
+ repo.updateDisplayName(account.getId(), CapabilitiesClient.getDisplayName(getApplicationContext(), ssoAccount, ApiProvider.getInstance()));
} catch (Throwable e) {
if (e instanceof NextcloudHttpRequestFailedException) {
if (((NextcloudHttpRequestFailedException) e).getStatusCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
Log.i(TAG, "Capabilities not modified.");
return Result.success();
- } else if(((NextcloudHttpRequestFailedException) e).getStatusCode() == HttpURLConnection.HTTP_UNAVAILABLE) {
+ } else if (((NextcloudHttpRequestFailedException) e).getStatusCode() == HttpURLConnection.HTTP_UNAVAILABLE) {
Log.i(TAG, "Server is in maintenance mode.");
return Result.success();
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesDatabase.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesDatabase.java
index 4f4aaa78..5cc50641 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesDatabase.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesDatabase.java
@@ -32,6 +32,8 @@ import it.niedermann.owncloud.notes.persistence.migration.Migration_17_18;
import it.niedermann.owncloud.notes.persistence.migration.Migration_18_19;
import it.niedermann.owncloud.notes.persistence.migration.Migration_19_20;
import it.niedermann.owncloud.notes.persistence.migration.Migration_20_21;
+import it.niedermann.owncloud.notes.persistence.migration.Migration_21_22;
+import it.niedermann.owncloud.notes.persistence.migration.Migration_22_23;
import it.niedermann.owncloud.notes.persistence.migration.Migration_9_10;
@Database(
@@ -41,7 +43,7 @@ import it.niedermann.owncloud.notes.persistence.migration.Migration_9_10;
CategoryOptions.class,
SingleNoteWidgetData.class,
NotesListWidgetData.class
- }, version = 21
+ }, version = 23
)
@TypeConverters({Converters.class})
public abstract class NotesDatabase extends RoomDatabase {
@@ -74,7 +76,9 @@ public abstract class NotesDatabase extends RoomDatabase {
new Migration_17_18(),
new Migration_18_19(context),
new Migration_19_20(context),
- new Migration_20_21()
+ new Migration_20_21(),
+ new Migration_21_22(context),
+ new Migration_22_23()
)
.fallbackToDestructiveMigrationOnDowngrade()
.fallbackToDestructiveMigration()
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java
index afd6145d..f36fd72a 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java
@@ -30,11 +30,9 @@ import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException;
import com.nextcloud.android.sso.helper.SingleAccountHelper;
import com.nextcloud.android.sso.model.SingleSignOnAccount;
-import org.json.JSONArray;
-import org.json.JSONException;
-
import java.util.ArrayList;
import java.util.Calendar;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -44,6 +42,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import it.niedermann.android.sharedpreferences.SharedPreferenceIntLiveData;
+import it.niedermann.owncloud.notes.BuildConfig;
import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.edit.EditNoteActivity;
import it.niedermann.owncloud.notes.persistence.entity.Account;
@@ -62,6 +61,7 @@ import it.niedermann.owncloud.notes.shared.model.ISyncCallback;
import it.niedermann.owncloud.notes.shared.model.NavigationCategory;
import it.niedermann.owncloud.notes.shared.model.NotesSettings;
import it.niedermann.owncloud.notes.shared.model.SyncResultStatus;
+import it.niedermann.owncloud.notes.shared.util.ApiVersionUtil;
import it.niedermann.owncloud.notes.shared.util.NoteUtil;
import it.niedermann.owncloud.notes.shared.util.SSOUtil;
import retrofit2.Call;
@@ -70,7 +70,6 @@ import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES.O;
import static androidx.lifecycle.Transformations.distinctUntilChanged;
import static androidx.lifecycle.Transformations.map;
-import static androidx.lifecycle.Transformations.switchMap;
import static it.niedermann.owncloud.notes.edit.EditNoteActivity.ACTION_SHORTCUT;
import static it.niedermann.owncloud.notes.shared.util.NoteUtil.generateNoteExcerpt;
import static it.niedermann.owncloud.notes.widget.notelist.NoteListWidget.updateNoteListWidgets;
@@ -84,6 +83,7 @@ public class NotesRepository {
private static NotesRepository instance;
+ private final ApiProvider apiProvider;
private final ExecutorService executor;
private final Context context;
private final NotesDatabase db;
@@ -137,22 +137,23 @@ public class NotesRepository {
public static synchronized NotesRepository getInstance(@NonNull Context context) {
if (instance == null) {
- instance = new NotesRepository(context, NotesDatabase.getInstance(context.getApplicationContext()), Executors.newCachedThreadPool());
+ instance = new NotesRepository(context, NotesDatabase.getInstance(context.getApplicationContext()), Executors.newCachedThreadPool(), ApiProvider.getInstance());
}
return instance;
}
- private NotesRepository(@NonNull final Context context, @NonNull final NotesDatabase db, @NonNull final ExecutorService executor) {
+ private NotesRepository(@NonNull final Context context, @NonNull final NotesDatabase db, @NonNull final ExecutorService executor, @NonNull ApiProvider apiProvider) {
this.context = context.getApplicationContext();
this.db = db;
this.executor = executor;
+ this.apiProvider = apiProvider;
this.defaultNonEmptyTitle = NoteUtil.generateNonEmptyNoteTitle("", this.context);
this.syncOnlyOnWifiKey = context.getApplicationContext().getResources().getString(R.string.pref_key_wifi_only);
// Registers BroadcastReceiver to track network connection changes.
- context.getApplicationContext().registerReceiver(networkReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
+ this.context.registerReceiver(networkReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
- final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.context.getApplicationContext());
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.context);
prefs.registerOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener);
syncOnlyOnWifi = prefs.getBoolean(syncOnlyOnWifiKey, false);
@@ -163,8 +164,8 @@ public class NotesRepository {
// Accounts
@AnyThread
- public void addAccount(@NonNull String url, @NonNull String username, @NonNull String accountName, @NonNull Capabilities capabilities, @NonNull IResponseCallback<Account> callback) {
- final Account createdAccount = db.getAccountDao().getAccountById(db.getAccountDao().insert(new Account(url, username, accountName, capabilities)));
+ public void addAccount(@NonNull String url, @NonNull String username, @NonNull String accountName, @NonNull Capabilities capabilities, @Nullable String displayName, @NonNull IResponseCallback<Account> callback) {
+ final Account createdAccount = db.getAccountDao().getAccountById(db.getAccountDao().insert(new Account(url, username, accountName, displayName, capabilities)));
if (createdAccount == null) {
callback.onError(new Exception("Could not read created account."));
} else {
@@ -180,10 +181,10 @@ public class NotesRepository {
@WorkerThread
public void deleteAccount(@NonNull Account account) {
try {
- ApiProvider.invalidateAPICache(AccountImporter.getSingleSignOnAccount(context, account.getAccountName()));
+ apiProvider.invalidateAPICache(AccountImporter.getSingleSignOnAccount(context, account.getAccountName()));
} catch (NextcloudFilesAppAccountNotFoundException e) {
e.printStackTrace();
- ApiProvider.invalidateAPICache();
+ apiProvider.invalidateAPICache();
}
db.getAccountDao().deleteAccount(account);
@@ -403,10 +404,12 @@ public class NotesRepository {
@MainThread
public LiveData<Note> moveNoteToAnotherAccount(Account account, @NonNull Note note) {
- return switchMap(db.getNoteDao().getContent$(note.getId()), (content) -> {
- final Note fullNote = new Note(null, note.getModified(), note.getTitle(), content, note.getCategory(), note.getFavorite(), null);
- deleteNoteAndSync(account, note.getId());
- return addNoteAndSync(account, fullNote);
+ final Note fullNote = new Note(null, note.getModified(), note.getTitle(), note.getContent(), note.getCategory(), note.getFavorite(), null);
+ deleteNoteAndSync(account, note.getId());
+ return map(addNoteAndSync(account, fullNote), (createdNote) -> {
+ db.getNoteDao().updateStatus(createdNote.getId(), DBStatus.LOCAL_EDITED);
+ createdNote.setStatus(DBStatus.LOCAL_EDITED);
+ return createdNote;
});
}
@@ -460,23 +463,27 @@ public class NotesRepository {
* @return changed {@link Note} if differs from database, otherwise the old {@link Note}.
*/
@WorkerThread
- public Note updateNoteAndSync(Account localAccount, @NonNull Note oldNote, @Nullable String newContent, @Nullable String newTitle, @Nullable ISyncCallback callback) {
+ public Note updateNoteAndSync(@NonNull Account localAccount, @NonNull Note oldNote, @Nullable String newContent, @Nullable String newTitle, @Nullable ISyncCallback callback) {
final Note newNote;
+ // Re-read the up to date remoteId from the database because the UI might not have the state after synchronization yet
+ // https://github.com/stefan-niedermann/nextcloud-notes/issues/1198
+ @Nullable final Long remoteId = db.getNoteDao().getRemoteId(oldNote.getId());
if (newContent == null) {
- newNote = new Note(oldNote.getId(), oldNote.getRemoteId(), oldNote.getModified(), oldNote.getTitle(), oldNote.getContent(), oldNote.getCategory(), oldNote.getFavorite(), oldNote.getETag(), DBStatus.LOCAL_EDITED, localAccount.getId(), oldNote.getExcerpt(), oldNote.getScrollY());
+ newNote = new Note(oldNote.getId(), remoteId, oldNote.getModified(), oldNote.getTitle(), oldNote.getContent(), oldNote.getCategory(), oldNote.getFavorite(), oldNote.getETag(), DBStatus.LOCAL_EDITED, localAccount.getId(), oldNote.getExcerpt(), oldNote.getScrollY());
} else {
final String title;
if (newTitle != null) {
title = newTitle;
} else {
- if ((oldNote.getRemoteId() == null || localAccount.getPreferredApiVersion() == null || localAccount.getPreferredApiVersion().compareTo(ApiVersion.API_VERSION_1_0) < 0) &&
+ final ApiVersion preferredApiVersion = ApiVersionUtil.getPreferredApiVersion(localAccount.getApiVersion());
+ if ((remoteId == null || preferredApiVersion == null || preferredApiVersion.compareTo(ApiVersion.API_VERSION_1_0) < 0) &&
(defaultNonEmptyTitle.equals(oldNote.getTitle()))) {
title = NoteUtil.generateNonEmptyNoteTitle(newContent, context);
} else {
title = oldNote.getTitle();
}
}
- newNote = new Note(oldNote.getId(), oldNote.getRemoteId(), Calendar.getInstance(), title, newContent, oldNote.getCategory(), oldNote.getFavorite(), oldNote.getETag(), DBStatus.LOCAL_EDITED, localAccount.getId(), generateNoteExcerpt(newContent, title), oldNote.getScrollY());
+ newNote = new Note(oldNote.getId(), remoteId, Calendar.getInstance(), title, newContent, oldNote.getCategory(), oldNote.getFavorite(), oldNote.getETag(), DBStatus.LOCAL_EDITED, localAccount.getId(), generateNoteExcerpt(newContent, title), oldNote.getScrollY());
}
int rows = db.getNoteDao().updateNote(newNote);
// if data was changed, set new status and schedule sync (with callback); otherwise invoke callback directly.
@@ -540,25 +547,25 @@ public class NotesRepository {
private void updateDynamicShortcuts(long accountId) {
executor.submit(() -> {
if (SDK_INT >= android.os.Build.VERSION_CODES.N_MR1) {
- ShortcutManager shortcutManager = context.getApplicationContext().getSystemService(ShortcutManager.class);
+ ShortcutManager shortcutManager = this.context.getSystemService(ShortcutManager.class);
if (shortcutManager != null) {
if (!shortcutManager.isRateLimitingActive()) {
List<ShortcutInfo> newShortcuts = new ArrayList<>();
for (Note note : db.getNoteDao().getRecentNotes(accountId)) {
if (!TextUtils.isEmpty(note.getTitle())) {
- Intent intent = new Intent(context.getApplicationContext(), EditNoteActivity.class);
+ Intent intent = new Intent(this.context, EditNoteActivity.class);
intent.putExtra(EditNoteActivity.PARAM_NOTE_ID, note.getId());
intent.setAction(ACTION_SHORTCUT);
- newShortcuts.add(new ShortcutInfo.Builder(context.getApplicationContext(), note.getId() + "")
+ newShortcuts.add(new ShortcutInfo.Builder(this.context, note.getId() + "")
.setShortLabel(note.getTitle() + "")
- .setIcon(Icon.createWithResource(context.getApplicationContext(), note.getFavorite() ? R.drawable.ic_star_yellow_24dp : R.drawable.ic_star_grey_ccc_24dp))
+ .setIcon(Icon.createWithResource(this.context, note.getFavorite() ? R.drawable.ic_star_yellow_24dp : R.drawable.ic_star_grey_ccc_24dp))
.setIntent(intent)
.build());
} else {
// Prevent crash https://github.com/stefan-niedermann/nextcloud-notes/issues/613
- Log.e(TAG, "shortLabel cannot be empty " + note);
+ Log.e(TAG, "shortLabel cannot be empty " + (BuildConfig.DEBUG ? note : note.getTitle()));
}
}
Log.d(TAG, "Update dynamic shortcuts");
@@ -571,40 +578,23 @@ public class NotesRepository {
}
/**
- * @param apiVersion has to be a JSON array as a string <code>["0.2", "1.0", ...]</code>
- * @return whether or not the given {@link ApiVersion} has been written to the database
- * @throws IllegalArgumentException if the apiVersion does not match the expected format
+ * @param raw has to be a JSON array as a string <code>["0.2", "1.0", ...]</code>
*/
- public boolean updateApiVersion(long accountId, @Nullable String apiVersion) throws IllegalArgumentException {
- if (apiVersion != null) {
- try {
- JSONArray apiVersions = new JSONArray(apiVersion);
- for (int i = 0; i < apiVersions.length(); i++) {
- ApiVersion.of(apiVersions.getString(i));
- }
- if (apiVersions.length() > 0) {
- final int updatedRows = db.getAccountDao().updateApiVersion(accountId, apiVersion);
- if (updatedRows == 0) {
- Log.d(TAG, "ApiVersion not updated, because it did not change");
- } else if (updatedRows == 1) {
- Log.i(TAG, "Updated apiVersion to \"" + apiVersion + "\" for accountId = " + accountId);
- ApiProvider.invalidateAPICache();
- } else {
- Log.w(TAG, "Updated " + updatedRows + " but expected only 1 for accountId = " + accountId + " and apiVersion = \"" + apiVersion + "\"");
- }
- return true;
- } else {
- Log.i(TAG, "Given API version is a valid JSON array but does not contain any valid API versions. Do not update database.");
- }
- } catch (NumberFormatException e) {
- throw new IllegalArgumentException("API version does contain a non-valid version: " + apiVersion);
- } catch (JSONException e) {
- throw new IllegalArgumentException("API version must contain be a JSON array: " + apiVersion);
+ public void updateApiVersion(long accountId, @Nullable String raw) {
+ final Collection<ApiVersion> apiVersions = ApiVersionUtil.parse(raw);
+ if (apiVersions.size() > 0) {
+ final int updatedRows = db.getAccountDao().updateApiVersion(accountId, ApiVersionUtil.serialize(apiVersions));
+ if (updatedRows == 0) {
+ Log.d(TAG, "ApiVersion not updated, because it did not change");
+ } else if (updatedRows == 1) {
+ Log.i(TAG, "Updated apiVersion to \"" + raw + "\" for accountId = " + accountId);
+ apiProvider.invalidateAPICache();
+ } else {
+ Log.w(TAG, "Updated " + updatedRows + " but expected only 1 for accountId = " + accountId + " and apiVersion = \"" + raw + "\"");
}
} else {
- Log.v(TAG, "Given API version is null. Do not update database");
+ Log.v(TAG, "Could not extract any version from the given String: " + raw);
}
- return false;
}
/**
@@ -709,7 +699,7 @@ public class NotesRepository {
@Override
protected void finalize() throws Throwable {
- context.getApplicationContext().unregisterReceiver(networkReceiver);
+ this.context.unregisterReceiver(networkReceiver);
super.finalize();
}
@@ -783,7 +773,7 @@ public class NotesRepository {
*
* @param onlyLocalChanges Whether to only push local changes to the server or to also load the whole list of notes from the server.
*/
- public synchronized void scheduleSync(Account account, boolean onlyLocalChanges) {
+ public synchronized void scheduleSync(@Nullable Account account, boolean onlyLocalChanges) {
if (account == null) {
Log.i(TAG, SingleSignOnAccount.class.getSimpleName() + " is null. Is this a local account?");
} else {
@@ -795,7 +785,7 @@ public class NotesRepository {
syncActive.put(account.getId(), true);
try {
Log.d(TAG, "... starting now");
- final NotesServerSyncTask syncTask = new NotesServerSyncTask(context, this, account, onlyLocalChanges) {
+ final NotesServerSyncTask syncTask = new NotesServerSyncTask(context, this, account, onlyLocalChanges, apiProvider) {
@Override
void onPreExecute() {
syncStatus.postValue(true);
@@ -873,7 +863,7 @@ public class NotesRepository {
public void updateNetworkStatus() {
try {
- final ConnectivityManager connMgr = (ConnectivityManager) context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
+ final ConnectivityManager connMgr = (ConnectivityManager) this.context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connMgr == null) {
throw new NetworkErrorException("ConnectivityManager is null");
}
@@ -927,4 +917,8 @@ public class NotesRepository {
public Call<NotesSettings> putServerSettings(@NonNull SingleSignOnAccount ssoAccount, @NonNull NotesSettings settings, @Nullable ApiVersion preferredApiVersion) {
return ApiProvider.getNotesAPI(context, ssoAccount, preferredApiVersion).putSettings(settings);
}
+
+ public void updateDisplayName(long id, @Nullable String displayName) {
+ db.getAccountDao().updateDisplayName(id, displayName);
+ }
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesServerSyncTask.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesServerSyncTask.java
index 88fb44f3..a7df68bd 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesServerSyncTask.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesServerSyncTask.java
@@ -7,6 +7,7 @@ import androidx.annotation.NonNull;
import com.nextcloud.android.sso.AccountImporter;
import com.nextcloud.android.sso.api.ParsedResponse;
+import com.nextcloud.android.sso.exceptions.NextcloudApiNotRespondingException;
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException;
import com.nextcloud.android.sso.exceptions.TokenMismatchException;
@@ -19,15 +20,16 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
+import it.niedermann.owncloud.notes.BuildConfig;
import it.niedermann.owncloud.notes.persistence.entity.Account;
import it.niedermann.owncloud.notes.persistence.entity.Note;
import it.niedermann.owncloud.notes.persistence.sync.NotesAPI;
import it.niedermann.owncloud.notes.shared.model.DBStatus;
import it.niedermann.owncloud.notes.shared.model.ISyncCallback;
import it.niedermann.owncloud.notes.shared.model.SyncResultStatus;
+import it.niedermann.owncloud.notes.shared.util.ApiVersionUtil;
import retrofit2.Response;
import static it.niedermann.owncloud.notes.shared.model.DBStatus.LOCAL_DELETED;
@@ -51,6 +53,8 @@ abstract class NotesServerSyncTask extends Thread {
private NotesAPI notesAPI;
@NonNull
+ private final ApiProvider apiProvider;
+ @NonNull
private final Context context;
@NonNull
private final NotesRepository repo;
@@ -64,13 +68,14 @@ abstract class NotesServerSyncTask extends Thread {
@NonNull
protected final ArrayList<Throwable> exceptions = new ArrayList<>();
- NotesServerSyncTask(@NonNull Context context, @NonNull NotesRepository repo, @NonNull Account localAccount, boolean onlyLocalChanges) throws NextcloudFilesAppAccountNotFoundException {
+ NotesServerSyncTask(@NonNull Context context, @NonNull NotesRepository repo, @NonNull Account localAccount, boolean onlyLocalChanges, @NonNull ApiProvider apiProvider) throws NextcloudFilesAppAccountNotFoundException {
super(TAG);
this.context = context;
this.repo = repo;
this.localAccount = localAccount;
this.ssoAccount = AccountImporter.getSingleSignOnAccount(context, localAccount.getAccountName());
this.onlyLocalChanges = onlyLocalChanges;
+ this.apiProvider = apiProvider;
}
void addCallbacks(Account account, List<ISyncCallback> callbacks) {
@@ -81,7 +86,7 @@ abstract class NotesServerSyncTask extends Thread {
public void run() {
onPreExecute();
- notesAPI = ApiProvider.getNotesAPI(context, ssoAccount, localAccount.getPreferredApiVersion());
+ notesAPI = apiProvider.getNotesAPI(context, ssoAccount, ApiVersionUtil.getPreferredApiVersion(localAccount.getApiVersion()));
Log.i(TAG, "STARTING SYNCHRONIZATION");
@@ -109,7 +114,7 @@ abstract class NotesServerSyncTask extends Thread {
boolean success = true;
final List<Note> notes = repo.getLocalModifiedNotes(localAccount.getId());
for (Note note : notes) {
- Log.d(TAG, " Process Local Note: " + note);
+ Log.d(TAG, " Process Local Note: " + (BuildConfig.DEBUG ? note : note.getTitle()));
try {
Note remoteNote;
switch (note.getStatus()) {
@@ -120,27 +125,37 @@ abstract class NotesServerSyncTask extends Thread {
final Response<Note> editResponse = notesAPI.editNote(note).execute();
if (editResponse.isSuccessful()) {
remoteNote = editResponse.body();
- } else {
- if (editResponse.code() == HTTP_NOT_FOUND) {
- Log.v(TAG, " ...Note does no longer exist on server → recreate");
- final Response<Note> createResponse = notesAPI.createNote(note).execute();
- if (createResponse.isSuccessful()) {
- remoteNote = createResponse.body();
- } else {
- throw new Exception(createResponse.errorBody().string());
+ if (remoteNote == null) {
+ Log.e(TAG, " ...Tried to edit \"" + note.getTitle() + "\" (#" + note.getId() + ") but the server response was null.");
+ throw new Exception("Server returned null after editing \"" + note.getTitle() + "\" (#" + note.getId() + ")");
+ }
+ } else if (editResponse.code() == HTTP_NOT_FOUND) {
+ Log.v(TAG, " ...Note does no longer exist on server → recreate");
+ final Response<Note> createResponse = notesAPI.createNote(note).execute();
+ if (createResponse.isSuccessful()) {
+ remoteNote = createResponse.body();
+ if (remoteNote == null) {
+ Log.e(TAG, " ...Tried to recreate \"" + note.getTitle() + "\" (#" + note.getId() + ") but the server response was null.");
+ throw new Exception("Server returned null after recreating \"" + note.getTitle() + "\" (#" + note.getId() + ")");
}
} else {
- throw new Exception(editResponse.errorBody().string());
+ throw new Exception(createResponse.message());
}
+ } else {
+ throw new Exception(editResponse.message());
}
} else {
Log.v(TAG, " ...Note does not have a remoteId yet → create");
final Response<Note> createResponse = notesAPI.createNote(note).execute();
if (createResponse.isSuccessful()) {
remoteNote = createResponse.body();
+ if (remoteNote == null) {
+ Log.e(TAG, " ...Tried to create \"" + note.getTitle() + "\" (#" + note.getId() + ") but the server response was null.");
+ throw new Exception("Server returned null after creating \"" + note.getTitle() + "\" (#" + note.getId() + ")");
+ }
repo.updateRemoteId(note.getId(), remoteNote.getRemoteId());
} else {
- throw new Exception(createResponse.errorBody().string());
+ throw new Exception(createResponse.message());
}
}
// Please note, that db.updateNote() realized an optimistic conflict resolution, which is required for parallel changes of this Note from the UI.
@@ -156,7 +171,7 @@ abstract class NotesServerSyncTask extends Thread {
if (deleteResponse.code() == HTTP_NOT_FOUND) {
Log.v(TAG, " ...delete (note has already been deleted remotely)");
} else {
- throw new Exception(deleteResponse.errorBody().string());
+ throw new Exception(deleteResponse.message());
}
}
}
@@ -175,7 +190,7 @@ abstract class NotesServerSyncTask extends Thread {
}
} catch (Exception e) {
if (e instanceof TokenMismatchException) {
- ApiProvider.invalidateAPICache(ssoAccount);
+ apiProvider.invalidateAPICache(ssoAccount);
}
exceptions.add(e);
success = false;
@@ -206,7 +221,7 @@ abstract class NotesServerSyncTask extends Thread {
final Set<Long> remoteIDs = new HashSet<>();
// pull remote changes: update or create each remote note
for (Note remoteNote : remoteNotes) {
- Log.v(TAG, " Process Remote Note: " + remoteNote);
+ Log.v(TAG, " Process Remote Note: " + (BuildConfig.DEBUG ? remoteNote : remoteNote.getTitle()));
remoteIDs.add(remoteNote.getRemoteId());
if (remoteNote.getModified() == null) {
Log.v(TAG, " ... unchanged");
@@ -217,7 +232,7 @@ abstract class NotesServerSyncTask extends Thread {
repo.updateIfNotModifiedLocallyAndAnyRemoteColumnHasChanged(
localId, remoteNote.getModified().getTimeInMillis(), remoteNote.getTitle(), remoteNote.getFavorite(), remoteNote.getCategory(), remoteNote.getETag(), remoteNote.getContent(), generateNoteExcerpt(remoteNote.getContent(), remoteNote.getTitle()));
} else {
- Log.e(TAG, "Tried to update note from server, but local id of note is null. " + remoteNote);
+ Log.e(TAG, "Tried to update note from server, but local id of note is null. " + (BuildConfig.DEBUG ? remoteNote : remoteNote.getTitle()));
}
} else {
Log.v(TAG, " ... create");
@@ -248,19 +263,10 @@ abstract class NotesServerSyncTask extends Thread {
repo.updateETag(localAccount.getId(), localAccount.getETag());
repo.updateModified(localAccount.getId(), localAccount.getModified().getTimeInMillis());
-
- String supportedApiVersions = null;
- final String supportedApiVersionsHeader = fetchResponse.getHeaders().get(HEADER_KEY_X_NOTES_API_VERSIONS);
- if (supportedApiVersionsHeader != null) {
- supportedApiVersions = "[" + Objects.requireNonNull(supportedApiVersionsHeader) + "]";
- }
- try {
- if (repo.updateApiVersion(localAccount.getId(), supportedApiVersions)) {
- localAccount.setApiVersion(supportedApiVersions);
- }
- } catch (Exception e) {
- exceptions.add(e);
- }
+ final String newApiVersion = ApiVersionUtil.sanitize(fetchResponse.getHeaders().get(HEADER_KEY_X_NOTES_API_VERSIONS));
+ localAccount.setApiVersion(newApiVersion);
+ repo.updateApiVersion(localAccount.getId(), newApiVersion);
+ Log.d(TAG, "ApiVersion: " + newApiVersion);
return true;
} catch (Throwable t) {
final Throwable cause = t.getCause();
@@ -274,6 +280,8 @@ abstract class NotesServerSyncTask extends Thread {
Log.d(TAG, "Server returned HTTP Status Code " + httpException.getStatusCode() + " - Server is in maintenance mode.");
return true;
}
+ } else if (cause.getClass() == NextcloudApiNotRespondingException.class || cause instanceof NextcloudApiNotRespondingException) {
+ apiProvider.invalidateAPICache(ssoAccount);
}
}
exceptions.add(t);
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/SyncWorker.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/SyncWorker.java
index 1d4a8bc7..adb7eff0 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/SyncWorker.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/SyncWorker.java
@@ -15,7 +15,6 @@ import androidx.work.WorkerParameters;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
-import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.persistence.entity.Account;
public class SyncWorker extends Worker {
@@ -44,22 +43,20 @@ public class SyncWorker extends Worker {
return Result.success();
}
- public static void update(@NonNull Context context, @NonNull String preferenceValue) {
+ /**
+ * Set up sync work to enabled every 15 minutes or just disabled
+ * https://github.com/stefan-niedermann/nextcloud-notes/issues/1168
+ * @param context the application
+ * @param backgroundSync the toggle result backgroundSync
+ */
+
+ public static void update(@NonNull Context context, boolean backgroundSync) {
deregister(context);
- if (!context.getString(R.string.pref_value_sync_off).equals(preferenceValue)) {
- int repeatInterval = 15;
- TimeUnit unit = TimeUnit.MINUTES;
- if (context.getString(R.string.pref_value_sync_1_hour).equals(preferenceValue)) {
- repeatInterval = 1;
- unit = TimeUnit.HOURS;
- } else if (context.getString(R.string.pref_value_sync_6_hours).equals(preferenceValue)) {
- repeatInterval = 6;
- unit = TimeUnit.HOURS;
- }
- PeriodicWorkRequest work = new PeriodicWorkRequest.Builder(SyncWorker.class, repeatInterval, unit)
+ if (backgroundSync) {
+ PeriodicWorkRequest work = new PeriodicWorkRequest.Builder(SyncWorker.class, 15, TimeUnit.MINUTES)
.setConstraints(constraints).build();
WorkManager.getInstance(context.getApplicationContext()).enqueueUniquePeriodicWork(WORKER_TAG, ExistingPeriodicWorkPolicy.REPLACE, work);
- Log.i(TAG, "Registering worker running each " + repeatInterval + " " + unit);
+ Log.i(TAG, "Registering worker running each " + 15 + " " + TimeUnit.MINUTES);
}
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/AccountDao.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/AccountDao.java
index 085c0a16..7723e1f0 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/AccountDao.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/AccountDao.java
@@ -1,6 +1,7 @@
package it.niedermann.owncloud.notes.persistence.dao;
import androidx.annotation.ColorInt;
+import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
@@ -18,10 +19,10 @@ public interface AccountDao {
long insert(Account localAccount);
@Delete
- int deleteAccount(Account localAccount);
+ void deleteAccount(Account localAccount);
- String getAccounts = "SELECT * FROM Account";
- String getAccountById = "SELECT * FROM Account WHERE ID = :accountId";
+ String getAccounts = "SELECT id, url, userName, accountName, eTag, modified, apiVersion, color, textColor, capabilitiesEtag, COALESCE(displayName, userName) as displayName FROM Account";
+ String getAccountById = "SELECT id, url, userName, accountName, eTag, modified, apiVersion, color, textColor, capabilitiesEtag, COALESCE(displayName, userName) as displayName FROM Account WHERE ID = :accountId";
@Query(getAccounts)
LiveData<List<Account>> getAccounts$();
@@ -35,7 +36,7 @@ public interface AccountDao {
@Query(getAccountById)
Account getAccountById(long accountId);
- @Query("SELECT * FROM Account WHERE ACCOUNTNAME = :accountName")
+ @Query("SELECT id, url, userName, accountName, eTag, modified, apiVersion, color, textColor, capabilitiesEtag, COALESCE(displayName, userName) as displayName FROM Account WHERE ACCOUNTNAME = :accountName")
Account getAccountByName(String accountName);
@Query("SELECT COUNT(*) FROM Account")
@@ -55,4 +56,7 @@ public interface AccountDao {
@Query("UPDATE Account SET APIVERSION = :apiVersion WHERE id = :id AND ((APIVERSION IS NULL AND :apiVersion IS NOT NULL) OR (APIVERSION IS NOT NULL AND :apiVersion IS NULL) OR APIVERSION <> :apiVersion)")
int updateApiVersion(Long id, String apiVersion);
+
+ @Query("UPDATE Account SET DISPLAYNAME = :displayName WHERE id = :id")
+ void updateDisplayName(long id, @Nullable String displayName);
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/NoteDao.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/NoteDao.java
index 618dff37..ca111727 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/NoteDao.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/NoteDao.java
@@ -29,11 +29,7 @@ public interface NoteDao {
@Update(onConflict = OnConflictStrategy.REPLACE)
int updateNote(Note newNote);
- @Query("DELETE FROM NOTE WHERE accountId = :accountId")
- int deleteByAccountId(Long accountId);
-
String getNoteById = "SELECT * FROM NOTE WHERE id = :id";
- String getContent = "SELECT content FROM NOTE WHERE id = :id";
String count = "SELECT COUNT(*) FROM NOTE WHERE status != 'LOCAL_DELETED' AND accountId = :accountId";
String countFavorites = "SELECT COUNT(*) FROM NOTE WHERE status != 'LOCAL_DELETED' AND accountId = :accountId AND favorite = 1";
String searchRecentByModified = "SELECT id, remoteId, accountId, title, favorite, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) ORDER BY favorite DESC, modified DESC";
@@ -51,6 +47,9 @@ public interface NoteDao {
@Query(getNoteById)
Note getNoteById(long id);
+ @Query("SELECT remoteId FROM NOTE WHERE id = :id")
+ Long getRemoteId(long id);
+
@Query(count)
LiveData<Integer> count$(long accountId);
@@ -63,12 +62,6 @@ public interface NoteDao {
@Query(countFavorites)
Integer countFavorites(long accountId);
- @Query(getContent)
- LiveData<String> getContent$(Long id);
-
- @Query(getContent)
- String getContent(Long id);
-
@Query(searchRecentByModified)
LiveData<List<Note>> searchRecentByModified$(long accountId, String query);
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/Account.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/Account.java
index 09f3fc26..016b2fd0 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/Account.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/Account.java
@@ -58,43 +58,21 @@ public class Account implements Serializable {
private int textColor = Color.WHITE;
@Nullable
private String capabilitiesETag;
+ @Nullable
+ private String displayName;
public Account() {
// Default constructor
}
- public Account(@NonNull String url, @NonNull String username, @NonNull String accountName, @NonNull Capabilities capabilities) {
+ public Account(@NonNull String url, @NonNull String username, @NonNull String accountName, @Nullable String displayName, @NonNull Capabilities capabilities) {
setUrl(url);
setUserName(username);
setAccountName(accountName);
+ setDisplayName(displayName);
setCapabilities(capabilities);
}
- @Nullable
- public ApiVersion getPreferredApiVersion() {
- // TODO move this logic to NotesClient?
- try {
- if (apiVersion == null) {
- return null;
- }
- final JSONArray versionsArray = new JSONArray(apiVersion);
- final Collection<ApiVersion> supportedApiVersions = new HashSet<>(versionsArray.length());
- for (int i = 0; i < versionsArray.length(); i++) {
- final ApiVersion parsedApiVersion = ApiVersion.of(versionsArray.getString(i));
- for (ApiVersion temp : ApiVersion.SUPPORTED_API_VERSIONS) {
- if (temp.equals(parsedApiVersion)) {
- supportedApiVersions.add(parsedApiVersion);
- break;
- }
- }
- }
- return Collections.max(supportedApiVersions);
- } catch (JSONException | NoSuchElementException e) {
- e.printStackTrace();
- return null;
- }
- }
-
public void setCapabilities(@NonNull Capabilities capabilities) {
capabilitiesETag = capabilities.getETag();
apiVersion = capabilities.getApiVersion();
@@ -189,6 +167,15 @@ public class Account implements Serializable {
this.capabilitiesETag = capabilitiesETag;
}
+ @Nullable
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public void setDisplayName(@Nullable String displayName) {
+ this.displayName = displayName;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -227,6 +214,7 @@ public class Account implements Serializable {
return result;
}
+ @NonNull
@Override
public String toString() {
return "Account{" +
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/Note.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/Note.java
index 376c099d..e0d0325c 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/Note.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/Note.java
@@ -255,6 +255,7 @@ public class Note implements Serializable, Item {
return result;
}
+ @NonNull
@Override
public String toString() {
return "Note{" +
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_14_15.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_14_15.java
index 6938e41c..a66fc0e9 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_14_15.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_14_15.java
@@ -12,10 +12,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase;
import java.util.Hashtable;
-import it.niedermann.owncloud.notes.shared.util.DatabaseIndexUtil;
-
public class Migration_14_15 extends Migration {
+ private static final String TAG = Migration_14_15.class.getSimpleName();
+
public Migration_14_15() {
super(14, 15);
}
@@ -43,14 +43,14 @@ public class Migration_14_15 extends Migration {
"EXCERPT TEXT NOT NULL DEFAULT '', " +
"FOREIGN KEY(CATEGORY) REFERENCES CATEGORIES(CATEGORY_ID), " +
"FOREIGN KEY(ACCOUNT_ID) REFERENCES ACCOUNTS(ID))");
- DatabaseIndexUtil.createIndex(db, "NOTES", "REMOTEID", "ACCOUNT_ID", "STATUS", "FAVORITE", "CATEGORY", "MODIFIED");
+ createIndex(db, "NOTES", "REMOTEID", "ACCOUNT_ID", "STATUS", "FAVORITE", "CATEGORY", "MODIFIED");
db.execSQL("CREATE TABLE CATEGORIES(" +
"CATEGORY_ID INTEGER PRIMARY KEY AUTOINCREMENT, " +
"CATEGORY_ACCOUNT_ID INTEGER NOT NULL, " +
"CATEGORY_TITLE TEXT NOT NULL, " +
"UNIQUE( CATEGORY_ACCOUNT_ID , CATEGORY_TITLE), " +
"FOREIGN KEY(CATEGORY_ACCOUNT_ID) REFERENCES ACCOUNTS(ID))");
- DatabaseIndexUtil.createIndex(db, "CATEGORIES", "CATEGORY_ID", "CATEGORY_ACCOUNT_ID", "CATEGORY_TITLE");
+ createIndex(db, "CATEGORIES", "CATEGORY_ID", "CATEGORY_ACCOUNT_ID", "CATEGORY_TITLE");
// A hashtable storing categoryTitle - categoryId Mapping
// This is used to prevent too many searches in database
Hashtable<String, Integer> categoryTitleIdMap = new Hashtable<>();
@@ -91,4 +91,16 @@ public class Migration_14_15 extends Migration {
tmpNotesCursor.close();
db.execSQL("DROP TABLE IF EXISTS " + tmpTableNotes);
}
+
+ private static void createIndex(@NonNull SupportSQLiteDatabase db, @NonNull String table, @NonNull String... columns) {
+ for (String column : columns) {
+ createIndex(db, table, column);
+ }
+ }
+
+ private static void createIndex(@NonNull SupportSQLiteDatabase db, @NonNull String table, @NonNull String column) {
+ String indexName = table + "_" + column + "_idx";
+ Log.v(TAG, "Creating database index: CREATE INDEX IF NOT EXISTS " + indexName + " ON " + table + "(" + column + ")");
+ db.execSQL("CREATE INDEX " + indexName + " ON " + table + "(" + column + ")");
+ }
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_18_19.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_18_19.java
index 343f9e81..576e2204 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_18_19.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_18_19.java
@@ -9,9 +9,13 @@ import androidx.sqlite.db.SupportSQLiteDatabase;
import com.bumptech.glide.Glide;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
public class Migration_18_19 extends Migration {
private static final String TAG = Migration_18_19.class.getSimpleName();
+ private final ExecutorService executor = Executors.newSingleThreadExecutor();
@NonNull
private final Context context;
@@ -27,9 +31,9 @@ public class Migration_18_19 extends Migration {
*/
@Override
public void migrate(@NonNull SupportSQLiteDatabase db) {
- new Thread(() -> {
+ executor.submit(() -> {
Log.i(TAG, "Clearing Glide disk cache");
Glide.get(context.getApplicationContext()).clearDiskCache();
- }).start();
+ });
}
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_21_22.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_21_22.java
new file mode 100644
index 00000000..f4413bba
--- /dev/null
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_21_22.java
@@ -0,0 +1,43 @@
+package it.niedermann.owncloud.notes.persistence.migration;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import androidx.annotation.NonNull;
+import androidx.preference.PreferenceManager;
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+import it.niedermann.owncloud.notes.persistence.SyncWorker;
+
+/**
+ * Enabling backgroundSync, set from {@link String} values to {@link Boolean} values
+ * https://github.com/stefan-niedermann/nextcloud-notes/issues/1168
+ */
+public class Migration_21_22 extends Migration {
+ @NonNull
+ private final Context context;
+
+ public Migration_21_22(@NonNull Context context) {
+ super(21, 22);
+ this.context = context;
+ }
+
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ if (sharedPreferences.contains("backgroundSync")) {
+ editor.remove("backgroundSync");
+ if (sharedPreferences.getString("backgroundSync", "").equals("off")) {
+ editor.putBoolean("backgroundSync", false);
+ } else {
+ editor.putBoolean("backgroundSync", true);
+ SyncWorker.update(context, true);
+ }
+ } else {
+ SyncWorker.update(context, true);
+ }
+ editor.apply();
+ }
+}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_22_23.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_22_23.java
new file mode 100644
index 00000000..b6a7494b
--- /dev/null
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_22_23.java
@@ -0,0 +1,101 @@
+package it.niedermann.owncloud.notes.persistence.migration;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.room.OnConflictStrategy;
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import it.niedermann.owncloud.notes.persistence.ApiProvider;
+import it.niedermann.owncloud.notes.persistence.entity.Account;
+import it.niedermann.owncloud.notes.shared.model.ApiVersion;
+
+/**
+ * Add <code>displayName</code> property to {@link Account}.
+ * <p>
+ * See: <a href="https://github.com/stefan-niedermann/nextcloud-notes/issues/1079">#1079 Show DisplayName instead of uid attribute for LDAP users</a>
+ * <p>
+ * Sanitizes the stored API versions in the database.
+ */
+public class Migration_22_23 extends Migration {
+
+ public Migration_22_23() {
+ super(22, 23);
+ }
+
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase db) {
+ addDisplayNameToAccounts(db);
+ sanitizeAccounts(db);
+ }
+
+ private static void addDisplayNameToAccounts(@NonNull SupportSQLiteDatabase db) {
+ db.execSQL("ALTER TABLE Account ADD COLUMN displayName TEXT");
+ }
+
+ private static void sanitizeAccounts(@NonNull SupportSQLiteDatabase db) {
+ final Cursor cursor = db.query("SELECT id, apiVersion FROM ACCOUNT", null);
+ final ContentValues values = new ContentValues(1);
+
+ final int COLUMN_POSITION_ID = cursor.getColumnIndex("id");
+ final int COLUMN_POSITION_API_VERSION = cursor.getColumnIndex("apiVersion");
+
+ while (cursor.moveToNext()) {
+ values.put("APIVERSION", sanitizeApiVersion(cursor.getString(COLUMN_POSITION_API_VERSION)));
+ db.update("ACCOUNT", OnConflictStrategy.REPLACE, values, "ID = ?", new String[]{String.valueOf(cursor.getLong(COLUMN_POSITION_ID))});
+ }
+ cursor.close();
+ ApiProvider.getInstance().invalidateAPICache();
+ }
+
+ @Nullable
+ public static String sanitizeApiVersion(@Nullable String raw) {
+ if (TextUtils.isEmpty(raw)) {
+ return null;
+ }
+
+ JSONArray a;
+ try {
+ a = new JSONArray(raw);
+ } catch (JSONException e) {
+ try {
+ a = new JSONArray("[" + raw + "]");
+ } catch (JSONException e1) {
+ return null;
+ }
+ }
+
+ final Collection<ApiVersion> result = new ArrayList<>();
+ for (int i = 0; i < a.length(); i++) {
+ try {
+ final ApiVersion version = ApiVersion.of(a.getString(i));
+ if (version.getMajor() != 0 || version.getMinor() != 0) {
+ result.add(version);
+ }
+ } catch (Exception ignored) {
+ }
+ }
+ if (result.isEmpty()) {
+ return null;
+ }
+ return "[" +
+ result
+ .stream()
+ .filter(Objects::nonNull)
+ .map(v -> v.getMajor() + "." + v.getMinor())
+ .collect(Collectors.joining(","))
+ + "]";
+ }
+}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/CapabilitiesDeserializer.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/CapabilitiesDeserializer.java
index c9bf78da..141443e3 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/CapabilitiesDeserializer.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/CapabilitiesDeserializer.java
@@ -1,77 +1,63 @@
package it.niedermann.owncloud.notes.persistence.sync;
import android.graphics.Color;
-import android.util.Log;
-import com.bumptech.glide.load.HttpException;
+import androidx.annotation.ColorInt;
+
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
-import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException;
import java.lang.reflect.Type;
import it.niedermann.android.util.ColorUtil;
import it.niedermann.owncloud.notes.shared.model.Capabilities;
-import static java.net.HttpURLConnection.HTTP_UNAVAILABLE;
-
+/**
+ * Deserialization of <code><a href="https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-api-overview.html?highlight=ocs#theming-capabilities">OcsCapabilities</a></code> to {@link Capabilities} is more complex than just mapping the JSON values to the Pojo properties.
+ *
+ * <ul>
+ * <li>The supported API versions of the Notes app are checked and <code>null</code>ed in case they are not present to maintain backward compatibility</li>
+ * <li>The color hex codes of the theming app are sanitized and mapped to {@link ColorInt}s</li>
+ * </ul>
+ */
public class CapabilitiesDeserializer implements JsonDeserializer<Capabilities> {
- private static final String TAG = CapabilitiesDeserializer.class.getSimpleName();
-
- private static final String JSON_OCS = "ocs";
- private static final String JSON_OCS_META = "meta";
- private static final String JSON_OCS_META_STATUSCODE = "statuscode";
- private static final String JSON_OCS_DATA = "data";
- private static final String JSON_OCS_DATA_CAPABILITIES = "capabilities";
- private static final String JSON_OCS_DATA_CAPABILITIES_NOTES = "notes";
- private static final String JSON_OCS_DATA_CAPABILITIES_NOTES_API_VERSION = "api_version";
- private static final String JSON_OCS_DATA_CAPABILITIES_THEMING = "theming";
- private static final String JSON_OCS_DATA_CAPABILITIES_THEMING_COLOR = "color";
- private static final String JSON_OCS_DATA_CAPABILITIES_THEMING_COLOR_TEXT = "color-text";
+ private static final String CAPABILITIES = "capabilities";
+ private static final String CAPABILITIES_NOTES = "notes";
+ private static final String CAPABILITIES_NOTES_API_VERSION = "api_version";
+ private static final String CAPABILITIES_THEMING = "theming";
+ private static final String CAPABILITIES_THEMING_COLOR = "color";
+ private static final String CAPABILITIES_THEMING_COLOR_TEXT = "color-text";
@Override
public Capabilities deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
final Capabilities response = new Capabilities();
- final JsonObject ocs = json.getAsJsonObject().getAsJsonObject(JSON_OCS);
- if (ocs.has(JSON_OCS_META)) {
- final JsonObject meta = ocs.getAsJsonObject(JSON_OCS_META);
- if (meta.has(JSON_OCS_META_STATUSCODE)) {
- if (meta.get(JSON_OCS_META_STATUSCODE).getAsInt() == HTTP_UNAVAILABLE) {
- Log.i(TAG, "Capabilities Endpoint: This instance is currently in maintenance mode.");
- throw new JsonParseException(new NextcloudHttpRequestFailedException(HTTP_UNAVAILABLE, new HttpException(HTTP_UNAVAILABLE)));
+ final JsonObject data = json.getAsJsonObject();
+ if (data.has(CAPABILITIES)) {
+ final JsonObject capabilities = data.getAsJsonObject(CAPABILITIES);
+ if (capabilities.has(CAPABILITIES_NOTES)) {
+ final JsonObject notes = capabilities.getAsJsonObject(CAPABILITIES_NOTES);
+ if (notes.has(CAPABILITIES_NOTES_API_VERSION)) {
+ response.setApiVersion(notes.get(CAPABILITIES_NOTES_API_VERSION).toString());
}
}
- }
- if (ocs.has(JSON_OCS_DATA)) {
- final JsonObject data = ocs.getAsJsonObject(JSON_OCS_DATA);
- if (data.has(JSON_OCS_DATA_CAPABILITIES)) {
- final JsonObject capabilities = data.getAsJsonObject(JSON_OCS_DATA_CAPABILITIES);
- if (capabilities.has(JSON_OCS_DATA_CAPABILITIES_NOTES)) {
- final JsonObject notes = capabilities.getAsJsonObject(JSON_OCS_DATA_CAPABILITIES_NOTES);
- if (notes.has(JSON_OCS_DATA_CAPABILITIES_NOTES_API_VERSION)) {
- final JsonElement apiVersion = notes.get(JSON_OCS_DATA_CAPABILITIES_NOTES_API_VERSION);
- response.setApiVersion(apiVersion.isJsonArray() ? apiVersion.toString() : null);
+ if (capabilities.has(CAPABILITIES_THEMING)) {
+ final JsonObject theming = capabilities.getAsJsonObject(CAPABILITIES_THEMING);
+ if (theming.has(CAPABILITIES_THEMING_COLOR)) {
+ try {
+ response.setColor(Color.parseColor(ColorUtil.INSTANCE.formatColorToParsableHexString(theming.get(CAPABILITIES_THEMING_COLOR).getAsString())));
+ } catch (Exception e) {
+ e.printStackTrace();
}
}
- if (capabilities.has(JSON_OCS_DATA_CAPABILITIES_THEMING)) {
- final JsonObject theming = capabilities.getAsJsonObject(JSON_OCS_DATA_CAPABILITIES_THEMING);
- if (theming.has(JSON_OCS_DATA_CAPABILITIES_THEMING_COLOR)) {
- try {
- response.setColor(Color.parseColor(ColorUtil.INSTANCE.formatColorToParsableHexString(theming.get(JSON_OCS_DATA_CAPABILITIES_THEMING_COLOR).getAsString())));
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- if (theming.has(JSON_OCS_DATA_CAPABILITIES_THEMING_COLOR_TEXT)) {
- try {
- response.setTextColor(Color.parseColor(ColorUtil.INSTANCE.formatColorToParsableHexString(theming.get(JSON_OCS_DATA_CAPABILITIES_THEMING_COLOR_TEXT).getAsString())));
- } catch (Exception e) {
- e.printStackTrace();
- }
+ if (theming.has(CAPABILITIES_THEMING_COLOR_TEXT)) {
+ try {
+ response.setTextColor(Color.parseColor(ColorUtil.INSTANCE.formatColorToParsableHexString(theming.get(CAPABILITIES_THEMING_COLOR_TEXT).getAsString())));
+ } catch (Exception e) {
+ e.printStackTrace();
}
}
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/NotesAPI.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/NotesAPI.java
index 4ab8371e..3e552ae6 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/NotesAPI.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/NotesAPI.java
@@ -80,10 +80,14 @@ public class NotesAPI {
}
public Call<Note> editNote(@NonNull Note note) {
+ final Long remoteId = note.getRemoteId();
+ if (remoteId == null) {
+ throw new IllegalArgumentException("remoteId of a " + Note.class.getSimpleName() + " must not be null if this object is used for editing a remote note.");
+ }
if (ApiVersion.API_VERSION_1_0.equals(usedApiVersion)) {
- return notesAPI_1_0.editNote(note, note.getRemoteId());
+ return notesAPI_1_0.editNote(note, remoteId);
} else if (ApiVersion.API_VERSION_0_2.equals(usedApiVersion)) {
- return notesAPI_0_2.editNote(new Note_0_2(note), note.getRemoteId());
+ return notesAPI_0_2.editNote(new Note_0_2(note), remoteId);
} else {
throw new UnsupportedOperationException("Used API version " + usedApiVersion + " does not support editNote().");
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/OcsAPI.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/OcsAPI.java
index 27ef57c4..e24ef7ef 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/OcsAPI.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/OcsAPI.java
@@ -5,8 +5,12 @@ import com.nextcloud.android.sso.api.ParsedResponse;
import io.reactivex.Observable;
import it.niedermann.owncloud.notes.shared.model.Capabilities;
+import it.niedermann.owncloud.notes.shared.model.OcsResponse;
+import it.niedermann.owncloud.notes.shared.model.OcsUser;
+import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Header;
+import retrofit2.http.Path;
/**
* @link <a href="https://deck.readthedocs.io/en/latest/API/">Deck REST API</a>
@@ -14,5 +18,8 @@ import retrofit2.http.Header;
public interface OcsAPI {
@GET("capabilities?format=json")
- Observable<ParsedResponse<Capabilities>> getCapabilities(@Header("If-None-Match") String eTag);
+ Observable<ParsedResponse<OcsResponse<Capabilities>>> getCapabilities(@Header("If-None-Match") String eTag);
+
+ @GET("users/{userId}?format=json")
+ Call<OcsResponse<OcsUser>> getUser(@Path("userId") String userId);
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/preferences/PreferencesActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/preferences/PreferencesActivity.java
index f872aac3..2dcd99ec 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/preferences/PreferencesActivity.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/preferences/PreferencesActivity.java
@@ -3,28 +3,28 @@ package it.niedermann.owncloud.notes.preferences;
import android.os.Bundle;
import androidx.annotation.Nullable;
+import androidx.lifecycle.ViewModelProvider;
import it.niedermann.owncloud.notes.LockedActivity;
import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.databinding.ActivityPreferencesBinding;
-/**
- * Allows to change application settings.
- */
-
public class PreferencesActivity extends LockedActivity {
+ private PreferencesViewModel viewModel;
private ActivityPreferencesBinding binding;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ viewModel = new ViewModelProvider(this).get(PreferencesViewModel.class);
+ viewModel.resultCode$.observe(this, this::setResult);
+
binding = ActivityPreferencesBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
- setResult(RESULT_CANCELED);
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container_view, new PreferencesFragment())
.commit();
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/preferences/PreferencesFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/preferences/PreferencesFragment.java
index 8af467fb..a38fd56c 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/preferences/PreferencesFragment.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/preferences/PreferencesFragment.java
@@ -8,6 +8,7 @@ import android.util.Log;
import androidx.annotation.ColorInt;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
+import androidx.lifecycle.ViewModelProvider;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
@@ -20,27 +21,25 @@ import it.niedermann.owncloud.notes.branding.BrandingUtil;
import it.niedermann.owncloud.notes.persistence.SyncWorker;
import it.niedermann.owncloud.notes.shared.util.DeviceCredentialUtil;
-import static it.niedermann.owncloud.notes.widget.notelist.NoteListWidget.updateNoteListWidgets;
-
public class PreferencesFragment extends PreferenceFragmentCompat implements Branded {
private static final String TAG = PreferencesFragment.class.getSimpleName();
+ private PreferencesViewModel viewModel;
+
private BrandedSwitchPreference fontPref;
private BrandedSwitchPreference lockPref;
private BrandedSwitchPreference wifiOnlyPref;
private BrandedSwitchPreference gridViewPref;
private BrandedSwitchPreference preventScreenCapturePref;
-
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
+ private BrandedSwitchPreference backgroundSyncPref;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.preferences);
+ viewModel = new ViewModelProvider(requireActivity()).get(PreferencesViewModel.class);
+
fontPref = findPreference(getString(R.string.pref_key_font));
gridViewPref = findPreference(getString(R.string.pref_key_gridview));
@@ -48,7 +47,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Bra
gridViewPref.setOnPreferenceChangeListener((Preference preference, Object newValue) -> {
final Boolean gridView = (Boolean) newValue;
Log.v(TAG, "gridView: " + gridView);
- requireActivity().setResult(Activity.RESULT_OK);
+ viewModel.resultCode$.setValue(Activity.RESULT_OK);
NotesApplication.updateGridViewEnabled(gridView);
return true;
});
@@ -79,7 +78,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Bra
assert themePref != null;
themePref.setOnPreferenceChangeListener((preference, newValue) -> {
NotesApplication.setAppTheme(DarkModeSetting.valueOf((String) newValue));
- requireActivity().setResult(Activity.RESULT_OK);
+ viewModel.resultCode$.setValue(Activity.RESULT_OK);
ActivityCompat.recreate(requireActivity());
return true;
});
@@ -91,11 +90,11 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Bra
return true;
});
- final ListPreference syncPref = findPreference(getString(R.string.pref_key_background_sync));
- assert syncPref != null;
- syncPref.setOnPreferenceChangeListener((preference, newValue) -> {
- Log.i(TAG, "syncPref: " + preference + " - newValue: " + newValue);
- SyncWorker.update(requireContext(), newValue.toString());
+ backgroundSyncPref = findPreference(getString(R.string.pref_key_background_sync));
+ assert backgroundSyncPref != null;
+ backgroundSyncPref.setOnPreferenceChangeListener((preference, newValue) -> {
+ Log.i(TAG, "backgroundSync: " + newValue);
+ SyncWorker.update(requireContext(), (Boolean) newValue);
return true;
});
}
@@ -112,6 +111,14 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Bra
}
}
+ /**
+ * Change color for backgroundSyncPref as well
+ * https://github.com/stefan-niedermann/nextcloud-deck/issues/531
+ *
+ * @param mainColor color of main brand
+ * @param textColor color of text
+ */
+
@Override
public void applyBrand(int mainColor, int textColor) {
fontPref.applyBrand(mainColor, textColor);
@@ -119,5 +126,6 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Bra
wifiOnlyPref.applyBrand(mainColor, textColor);
gridViewPref.applyBrand(mainColor, textColor);
preventScreenCapturePref.applyBrand(mainColor, textColor);
+ backgroundSyncPref.applyBrand(mainColor, textColor);
}
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/preferences/PreferencesViewModel.java b/app/src/main/java/it/niedermann/owncloud/notes/preferences/PreferencesViewModel.java
new file mode 100644
index 00000000..dfde6c92
--- /dev/null
+++ b/app/src/main/java/it/niedermann/owncloud/notes/preferences/PreferencesViewModel.java
@@ -0,0 +1,9 @@
+package it.niedermann.owncloud.notes.preferences;
+
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.ViewModel;
+
+public class PreferencesViewModel extends ViewModel {
+
+ public final MutableLiveData<Integer> resultCode$ = new MutableLiveData<>();
+}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/account/AccountChooserViewHolder.java b/app/src/main/java/it/niedermann/owncloud/notes/shared/account/AccountChooserViewHolder.java
index 7688b1b1..5d3d2963 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/shared/account/AccountChooserViewHolder.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/shared/account/AccountChooserViewHolder.java
@@ -31,7 +31,7 @@ public class AccountChooserViewHolder extends RecyclerView.ViewHolder {
.into(binding.accountItemAvatar);
binding.accountLayout.setOnClickListener((v) -> targetAccountConsumer.accept(localAccount));
- binding.accountName.setText(localAccount.getUserName());
+ binding.accountName.setText(localAccount.getDisplayName());
binding.accountHost.setText(Uri.parse(localAccount.getUrl()).getHost());
}
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/model/ApiVersion.java b/app/src/main/java/it/niedermann/owncloud/notes/shared/model/ApiVersion.java
index ee2fdc3a..589f2571 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/shared/model/ApiVersion.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/shared/model/ApiVersion.java
@@ -42,10 +42,6 @@ public class ApiVersion implements Comparable<ApiVersion> {
return minor;
}
- public String getOriginalVersion() {
- return originalVersion;
- }
-
public static ApiVersion of(String versionString) {
int major = 0, minor = 0;
if (versionString != null) {
@@ -90,6 +86,9 @@ public class ApiVersion implements Comparable<ApiVersion> {
// return getMajor() >= 1 && getMinor() >= 2;
}
+ /**
+ * Checks only the <strong>{@link #major}</strong> version.
+ */
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/model/Capabilities.java b/app/src/main/java/it/niedermann/owncloud/notes/shared/model/Capabilities.java
index 5514a91b..06bd867d 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/shared/model/Capabilities.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/shared/model/Capabilities.java
@@ -1,103 +1,21 @@
package it.niedermann.owncloud.notes.shared.model;
import android.graphics.Color;
-import android.util.Log;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-import com.bumptech.glide.load.HttpException;
-import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import it.niedermann.android.util.ColorUtil;
-
-import static java.net.HttpURLConnection.HTTP_UNAVAILABLE;
-
-/**
- * This entity class is used to return relevant data of the HTTP response.
- */
public class Capabilities {
- private static final String TAG = Capabilities.class.getSimpleName();
-
- private static final String JSON_OCS = "ocs";
- private static final String JSON_OCS_META = "meta";
- private static final String JSON_OCS_META_STATUSCODE = "statuscode";
- private static final String JSON_OCS_DATA = "data";
- private static final String JSON_OCS_DATA_CAPABILITIES = "capabilities";
- private static final String JSON_OCS_DATA_CAPABILITIES_NOTES = "notes";
- private static final String JSON_OCS_DATA_CAPABILITIES_NOTES_API_VERSION = "api_version";
- private static final String JSON_OCS_DATA_CAPABILITIES_THEMING = "theming";
- private static final String JSON_OCS_DATA_CAPABILITIES_THEMING_COLOR = "color";
- private static final String JSON_OCS_DATA_CAPABILITIES_THEMING_COLOR_TEXT = "color-text";
-
private String apiVersion = null;
-
@ColorInt
- private int color = -16743735;
+ private int color = -16743735; // #0082C9
@ColorInt
- private int textColor = -16777216;
+ private int textColor = Color.WHITE;
@Nullable
private String eTag;
- public Capabilities() {
-
- }
-
- @VisibleForTesting
- public Capabilities(@NonNull String response, @Nullable String eTag) throws NextcloudHttpRequestFailedException {
- this.eTag = eTag;
- final JSONObject ocs;
- try {
- ocs = new JSONObject(response).getJSONObject(JSON_OCS);
- if (ocs.has(JSON_OCS_META)) {
- final JSONObject meta = ocs.getJSONObject(JSON_OCS_META);
- if (meta.has(JSON_OCS_META_STATUSCODE)) {
- if (meta.getInt(JSON_OCS_META_STATUSCODE) == HTTP_UNAVAILABLE) {
- Log.i(TAG, "Capabilities Endpoint: This instance is currently in maintenance mode.");
- throw new NextcloudHttpRequestFailedException(HTTP_UNAVAILABLE, new HttpException(HTTP_UNAVAILABLE));
- }
- }
- }
- if (ocs.has(JSON_OCS_DATA)) {
- final JSONObject data = ocs.getJSONObject(JSON_OCS_DATA);
- if (data.has(JSON_OCS_DATA_CAPABILITIES)) {
- final JSONObject capabilities = data.getJSONObject(JSON_OCS_DATA_CAPABILITIES);
- if (capabilities.has(JSON_OCS_DATA_CAPABILITIES_NOTES)) {
- final JSONObject notes = capabilities.getJSONObject(JSON_OCS_DATA_CAPABILITIES_NOTES);
- if (notes.has(JSON_OCS_DATA_CAPABILITIES_NOTES_API_VERSION)) {
- this.apiVersion = notes.getString(JSON_OCS_DATA_CAPABILITIES_NOTES_API_VERSION);
- }
- }
- if (capabilities.has(JSON_OCS_DATA_CAPABILITIES_THEMING)) {
- final JSONObject theming = capabilities.getJSONObject(JSON_OCS_DATA_CAPABILITIES_THEMING);
- if (theming.has(JSON_OCS_DATA_CAPABILITIES_THEMING_COLOR)) {
- try {
- this.color = Color.parseColor(ColorUtil.INSTANCE.formatColorToParsableHexString(theming.getString(JSON_OCS_DATA_CAPABILITIES_THEMING_COLOR)));
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- if (theming.has(JSON_OCS_DATA_CAPABILITIES_THEMING_COLOR_TEXT)) {
- try {
- this.textColor = Color.parseColor(ColorUtil.INSTANCE.formatColorToParsableHexString(theming.getString(JSON_OCS_DATA_CAPABILITIES_THEMING_COLOR_TEXT)));
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- }
- }
- } catch (JSONException e) {
- e.printStackTrace();
- }
- }
-
public void setApiVersion(String apiVersion) {
this.apiVersion = apiVersion;
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/model/CategorySortingMethod.java b/app/src/main/java/it/niedermann/owncloud/notes/shared/model/CategorySortingMethod.java
index 94bcda38..6a36ade1 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/shared/model/CategorySortingMethod.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/shared/model/CategorySortingMethod.java
@@ -7,7 +7,7 @@ public enum CategorySortingMethod {
private final int id;
private final String title; // sorting method OrderBy for SQL
- /***
+ /**
* Constructor
* @param title given sorting method OrderBy
*/
@@ -16,7 +16,7 @@ public enum CategorySortingMethod {
this.title = title;
}
- /***
+ /**
* Retrieve the sorting method id represented in database
* @return the sorting method id for the enum item
*/
@@ -24,7 +24,7 @@ public enum CategorySortingMethod {
return this.id;
}
- /***
+ /**
* Retrieve the sorting method order for SQL
* @return the sorting method order for the enum item
*/
@@ -32,7 +32,7 @@ public enum CategorySortingMethod {
return this.title;
}
- /***
+ /**
* Retrieve the corresponding enum value with given the index (ordinal)
* @param id the id of the corresponding enum value stored in DB
* @return the corresponding enum item with the index (ordinal)
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/model/OcsResponse.java b/app/src/main/java/it/niedermann/owncloud/notes/shared/model/OcsResponse.java
new file mode 100644
index 00000000..0fea9a92
--- /dev/null
+++ b/app/src/main/java/it/niedermann/owncloud/notes/shared/model/OcsResponse.java
@@ -0,0 +1,30 @@
+package it.niedermann.owncloud.notes.shared.model;
+
+import com.google.gson.annotations.Expose;
+
+/**
+ * <a href="https://www.open-collaboration-services.org/">OpenCollaborationServices</a>
+ *
+ * @param <T> defines the payload of this {@link OcsResponse}.
+ */
+public class OcsResponse<T> {
+
+ @Expose
+ public OcsWrapper<T> ocs;
+
+ public static class OcsWrapper<T> {
+ @Expose
+ public OcsMeta meta;
+ @Expose
+ public T data;
+ }
+
+ public static class OcsMeta {
+ @Expose
+ public String status;
+ @Expose
+ public int statuscode;
+ @Expose
+ public String message;
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/model/OcsUser.java b/app/src/main/java/it/niedermann/owncloud/notes/shared/model/OcsUser.java
new file mode 100644
index 00000000..9248abdf
--- /dev/null
+++ b/app/src/main/java/it/niedermann/owncloud/notes/shared/model/OcsUser.java
@@ -0,0 +1,16 @@
+package it.niedermann.owncloud.notes.shared.model;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Equivalent of an <code><a href="https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-api-overview.html?highlight=ocs#user-metadata">OcsUser</a></code>
+ */
+public class OcsUser {
+ @Expose
+ @SerializedName("id")
+ public String userId;
+ @Expose
+ @SerializedName("displayname")
+ public String displayName;
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/ApiVersionUtil.java b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/ApiVersionUtil.java
new file mode 100644
index 00000000..57788472
--- /dev/null
+++ b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/ApiVersionUtil.java
@@ -0,0 +1,105 @@
+package it.niedermann.owncloud.notes.shared.util;
+
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import it.niedermann.owncloud.notes.shared.model.ApiVersion;
+
+public class ApiVersionUtil {
+
+ private ApiVersionUtil() {
+ throw new UnsupportedOperationException("Do not instantiate this util class.");
+ }
+
+ /**
+ * @return a {@link Collection} of all valid {@link ApiVersion}s which have been found in {@param raw}.
+ */
+ @NonNull
+ public static Collection<ApiVersion> parse(@Nullable String raw) {
+ if (TextUtils.isEmpty(raw)) {
+ return Collections.emptyList();
+ }
+
+ JSONArray a;
+ try {
+ a = new JSONArray(raw);
+ } catch (JSONException e) {
+ try {
+ a = new JSONArray("[" + raw + "]");
+ } catch (JSONException e1) {
+ return Collections.emptyList();
+ }
+ }
+
+ final Collection<ApiVersion> result = new ArrayList<>();
+ for (int i = 0; i < a.length(); i++) {
+ try {
+ final ApiVersion version = ApiVersion.of(a.getString(i));
+ if (version.getMajor() != 0 || version.getMinor() != 0) {
+ result.add(version);
+ }
+ } catch (Exception ignored) {
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @return a serialized {@link String} of the given {@param apiVersions} or <code>null</code>.
+ */
+ @Nullable
+ public static String serialize(@Nullable Collection<ApiVersion> apiVersions) {
+ if (apiVersions == null || apiVersions.isEmpty()) {
+ return null;
+ }
+ return "[" +
+ apiVersions
+ .stream()
+ .filter(Objects::nonNull)
+ .map(v -> v.getMajor() + "." + v.getMinor())
+ .collect(Collectors.joining(","))
+ + "]";
+ }
+
+ @Nullable
+ public static String sanitize(@Nullable String raw) {
+ return serialize(parse(raw));
+ }
+
+ /**
+ * @return the highest {@link ApiVersion} that is supported by the server according to {@param raw},
+ * whose major version is also supported by this app (see {@link ApiVersion#SUPPORTED_API_VERSIONS}).
+ * Returns <code>null</code> if no better version could be found.
+ */
+ @Nullable
+ public static ApiVersion getPreferredApiVersion(@Nullable String raw) {
+ return parse(raw)
+ .stream()
+ .filter(version -> Arrays.asList(ApiVersion.SUPPORTED_API_VERSIONS).contains(version))
+ .max((o1, o2) -> {
+ if (o2.getMajor() > o1.getMajor()) {
+ return -1;
+ } else if (o2.getMajor() < o1.getMajor()) {
+ return 1;
+ } else if (o2.getMinor() > o1.getMinor()) {
+ return -1;
+ } else if (o2.getMinor() < o1.getMinor()) {
+ return 1;
+ }
+ return 0;
+ })
+ .orElse(null);
+ }
+}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/CustomAppGlideModule.java b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/CustomAppGlideModule.java
index 03eb1097..35625119 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/CustomAppGlideModule.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/CustomAppGlideModule.java
@@ -1,18 +1,37 @@
package it.niedermann.owncloud.notes.shared.util;
import android.content.Context;
+import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.annotation.UiThread;
import com.bumptech.glide.Glide;
import com.bumptech.glide.Registry;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.module.AppGlideModule;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
@GlideModule
public class CustomAppGlideModule extends AppGlideModule {
+
+ private static final String TAG = CustomAppGlideModule.class.getSimpleName();
+ private static final ExecutorService clearDiskCacheExecutor = Executors.newSingleThreadExecutor();
+
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
super.registerComponents(context, glide, registry);
}
+
+ @UiThread
+ public static void clearCache(@NonNull Context context) {
+ Log.i(TAG, "Clearing Glide memory cache");
+ Glide.get(context).clearMemory();
+ clearDiskCacheExecutor.submit(() -> {
+ Log.i(TAG, "Clearing Glide disk cache");
+ Glide.get(context.getApplicationContext()).clearDiskCache();
+ });
+ }
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/DatabaseIndexUtil.java b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/DatabaseIndexUtil.java
deleted file mode 100644
index 8506c713..00000000
--- a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/DatabaseIndexUtil.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package it.niedermann.owncloud.notes.shared.util;
-
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.sqlite.db.SupportSQLiteDatabase;
-
-public class DatabaseIndexUtil {
-
- private static final String TAG = DatabaseIndexUtil.class.getSimpleName();
-
- private DatabaseIndexUtil() {
-
- }
-
- public static void createIndex(@NonNull SupportSQLiteDatabase db, @NonNull String table, @NonNull String... columns) {
- for (String column : columns) {
- createIndex(db, table, column);
- }
- }
-
- public static void createIndex(@NonNull SupportSQLiteDatabase db, @NonNull String table, @NonNull String column) {
- String indexName = table + "_" + column + "_idx";
- Log.v(TAG, "Creating database index: CREATE INDEX IF NOT EXISTS " + indexName + " ON " + table + "(" + column + ")");
- db.execSQL("CREATE INDEX " + indexName + " ON " + table + "(" + column + ")");
- }
-
- public static void dropIndexes(@NonNull SupportSQLiteDatabase db) {
- try (Cursor c = db.query("SELECT name, sql FROM sqlite_master WHERE type = 'index'")) {
- while (c.moveToNext()) {
- // Skip automatic indexes which we can't drop manually
- if (c.getString(1) != null) {
- Log.v(TAG, "Deleting database index: DROP INDEX " + c.getString(0));
- db.execSQL("DROP INDEX " + c.getString(0));
- }
- }
- }
- }
-}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/DeviceCredentialUtil.java b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/DeviceCredentialUtil.java
index 034eea5a..14163e91 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/DeviceCredentialUtil.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/DeviceCredentialUtil.java
@@ -12,7 +12,7 @@ public class DeviceCredentialUtil {
private static final String TAG = DeviceCredentialUtil.class.getSimpleName();
private DeviceCredentialUtil() {
- // utility class -> private constructor
+ throw new UnsupportedOperationException("Do not instantiate this util class.");
}
public static boolean areCredentialsAvailable(Context context) {
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/DisplayUtils.java b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/DisplayUtils.java
index ad6b6793..06cbf57d 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/DisplayUtils.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/DisplayUtils.java
@@ -2,27 +2,21 @@ package it.niedermann.owncloud.notes.shared.util;
import android.content.Context;
import android.content.res.Resources;
-import android.graphics.Color;
-import android.text.Spannable;
-import android.text.TextPaint;
-import android.text.TextUtils;
-import android.text.style.MetricAffectingSpan;
+import android.graphics.Rect;
+import android.os.Build;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.WindowInsets;
-import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.content.ContextCompat;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
import java.util.Collection;
import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
import java.util.stream.Collectors;
-import it.niedermann.android.util.ColorUtil;
-import it.niedermann.owncloud.notes.NotesApplication;
import it.niedermann.owncloud.notes.R;
-import it.niedermann.owncloud.notes.branding.BrandingUtil;
import it.niedermann.owncloud.notes.main.navigation.NavigationAdapter;
import it.niedermann.owncloud.notes.main.navigation.NavigationItem;
import it.niedermann.owncloud.notes.persistence.entity.CategoryWithNotesCount;
@@ -30,7 +24,7 @@ import it.niedermann.owncloud.notes.persistence.entity.CategoryWithNotesCount;
public class DisplayUtils {
private DisplayUtils() {
-
+ throw new UnsupportedOperationException("Do not instantiate this util class.");
}
public static List<NavigationItem.CategoryNavigationItem> convertToCategoryNavigationItem(@NonNull Context context, @NonNull Collection<CategoryWithNotesCount> counter) {
@@ -52,4 +46,29 @@ public class DisplayUtils {
}
return new NavigationItem.CategoryNavigationItem("category:" + counter.getCategory(), counter.getCategory(), counter.getTotalNotes(), icon, counter.getAccountId(), counter.getCategory());
}
+
+ /**
+ * Detect if the soft keyboard is open.
+ * On API prior to 30 we fall back to workaround which might be less reliable
+ *
+ * @param parentView View
+ * @return keyboardVisibility Boolean
+ */
+ public static boolean isSoftKeyboardVisible(@NonNull View parentView) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ final WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(parentView);
+ if (insets != null) {
+ return insets.isVisible(WindowInsets.Type.ime());
+ }
+ }
+
+ //Arbitrary keyboard height
+ final int defaultKeyboardHeightDP = 100;
+ final int EstimatedKeyboardDP = defaultKeyboardHeightDP + (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? 48 : 0);
+ final Rect rect = new Rect();
+ final int estimatedKeyboardHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, EstimatedKeyboardDP, parentView.getResources().getDisplayMetrics());
+ parentView.getWindowVisibleDisplayFrame(rect);
+ final int heightDiff = parentView.getRootView().getHeight() - (rect.bottom - rect.top);
+ return heightDiff >= estimatedKeyboardHeight;
+ }
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/NoteUtil.java b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/NoteUtil.java
index e5b8afae..3d5a118c 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/NoteUtil.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/NoteUtil.java
@@ -22,7 +22,7 @@ public class NoteUtil {
public static final String EXCERPT_LINE_SEPARATOR = " ";
private NoteUtil() {
-
+ throw new UnsupportedOperationException("Do not instantiate this util class.");
}
/**
@@ -117,7 +117,7 @@ public class NoteUtil {
line = removeMarkdown(lines[currentLine]);
}
} else {
- line = content;
+ line = removeMarkdown(content);
}
return line;
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/NotesColorUtil.java b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/NotesColorUtil.java
index a445208b..42aaf79d 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/NotesColorUtil.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/NotesColorUtil.java
@@ -14,6 +14,7 @@ public final class NotesColorUtil {
private static final Map<ColorPair, Boolean> CONTRAST_RATIO_SUFFICIENT_CACHE = new HashMap<>();
private NotesColorUtil() {
+ throw new UnsupportedOperationException("Do not instantiate this util class.");
}
public static boolean contrastRatioIsSufficient(@ColorInt int colorOne, @ColorInt int colorTwo) {
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/SSOUtil.java b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/SSOUtil.java
index da529136..1e2542b0 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/SSOUtil.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/SSOUtil.java
@@ -19,7 +19,7 @@ public class SSOUtil {
private static final String TAG = SSOUtil.class.getSimpleName();
private SSOUtil() {
-
+ throw new UnsupportedOperationException("Do not instantiate this util class.");
}
/**
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/ShareUtil.java b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/ShareUtil.java
index 115d18dd..4b7aebd9 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/ShareUtil.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/ShareUtil.java
@@ -14,6 +14,11 @@ import it.niedermann.android.markdown.MarkdownUtil;
import static android.content.ClipDescription.MIMETYPE_TEXT_PLAIN;
public class ShareUtil {
+
+ private ShareUtil() {
+ throw new UnsupportedOperationException("Do not instantiate this util class.");
+ }
+
public static void openShareDialog(@NonNull Context context, @Nullable String subject, @Nullable String text) {
context.startActivity(Intent.createChooser(new Intent()
.setAction(Intent.ACTION_SEND)
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/SupportUtil.java b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/SupportUtil.java
index 8bf80cb9..27fec716 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/SupportUtil.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/SupportUtil.java
@@ -13,7 +13,7 @@ import androidx.core.text.HtmlCompat;
public class SupportUtil {
private SupportUtil() {
-
+ throw new UnsupportedOperationException("Do not instantiate this util class.");
}
/**
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListWidget.java b/app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListWidget.java
index 7cc84de3..8825ad98 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListWidget.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListWidget.java
@@ -11,6 +11,8 @@ import android.util.Log;
import android.widget.RemoteViews;
import java.util.NoSuchElementException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.persistence.NotesRepository;
@@ -18,6 +20,7 @@ import it.niedermann.owncloud.notes.persistence.entity.NotesListWidgetData;
public class NoteListWidget extends AppWidgetProvider {
private static final String TAG = NoteListWidget.class.getSimpleName();
+ private final ExecutorService executor = Executors.newCachedThreadPool();
static void updateAppWidget(Context context, AppWidgetManager awm, int[] appWidgetIds) {
final NotesRepository repo = NotesRepository.getInstance(context);
@@ -83,7 +86,7 @@ public class NoteListWidget extends AppWidgetProvider {
final NotesRepository repo = NotesRepository.getInstance(context);
for (int appWidgetId : appWidgetIds) {
- new Thread(() -> repo.removeNoteListWidget(appWidgetId)).start();
+ executor.submit(() -> repo.removeNoteListWidget(appWidgetId));
}
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListWidgetConfigurationActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListWidgetConfigurationActivity.java
index a750ee1e..3d7122da 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListWidgetConfigurationActivity.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListWidgetConfigurationActivity.java
@@ -14,6 +14,9 @@ import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundExce
import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException;
import com.nextcloud.android.sso.helper.SingleAccountHelper;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
import it.niedermann.owncloud.notes.LockedActivity;
import it.niedermann.owncloud.notes.NotesApplication;
import it.niedermann.owncloud.notes.R;
@@ -34,6 +37,8 @@ import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.
public class NoteListWidgetConfigurationActivity extends LockedActivity {
private static final String TAG = Activity.class.getSimpleName();
+ private final ExecutorService executor = Executors.newCachedThreadPool();
+
private int appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
private Account localAccount = null;
@@ -104,7 +109,7 @@ public class NoteListWidgetConfigurationActivity extends LockedActivity {
data.setAccountId(localAccount.getId());
data.setThemeMode(NotesApplication.getAppTheme(getApplicationContext()).getModeId());
- new Thread(() -> {
+ executor.submit(() -> {
repo.createOrUpdateNoteListWidgetData(data);
final Intent updateIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE, null, getApplicationContext(), NoteListWidget.class)
@@ -112,7 +117,7 @@ public class NoteListWidgetConfigurationActivity extends LockedActivity {
setResult(RESULT_OK, updateIntent);
getApplicationContext().sendBroadcast(updateIntent);
finish();
- }).start();
+ });
}
public void onIconClick(NavigationItem item) {
@@ -122,7 +127,7 @@ public class NoteListWidgetConfigurationActivity extends LockedActivity {
binding.recyclerView.setAdapter(adapterCategories);
- new Thread(() -> {
+ executor.submit(() -> {
try {
this.localAccount = repo.getAccountByName(SingleAccountHelper.getCurrentSingleSignOnAccount(this).name);
} catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
@@ -133,7 +138,7 @@ public class NoteListWidgetConfigurationActivity extends LockedActivity {
finish();
}
runOnUiThread(() -> viewModel.getAdapterCategories(localAccount.getId()).observe(this, (navigationItems) -> adapterCategories.setItems(navigationItems)));
- }).start();
+ });
}
@Override
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/widget/singlenote/SingleNoteWidget.java b/app/src/main/java/it/niedermann/owncloud/notes/widget/singlenote/SingleNoteWidget.java
index e208603a..106b9f93 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/widget/singlenote/SingleNoteWidget.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/widget/singlenote/SingleNoteWidget.java
@@ -10,6 +10,9 @@ import android.net.Uri;
import android.util.Log;
import android.widget.RemoteViews;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.edit.BaseNoteFragment;
import it.niedermann.owncloud.notes.edit.EditNoteActivity;
@@ -19,6 +22,7 @@ import it.niedermann.owncloud.notes.persistence.entity.SingleNoteWidgetData;
public class SingleNoteWidget extends AppWidgetProvider {
private static final String TAG = SingleNoteWidget.class.getSimpleName();
+ private final ExecutorService executor = Executors.newCachedThreadPool();
static void updateAppWidget(Context context, AppWidgetManager awm, int[] appWidgetIds) {
final Intent templateIntent = new Intent(context, EditNoteActivity.class);
@@ -69,7 +73,7 @@ public class SingleNoteWidget extends AppWidgetProvider {
final NotesRepository repo = NotesRepository.getInstance(context);
for (int appWidgetId : appWidgetIds) {
- new Thread(() -> repo.removeSingleNoteWidget(appWidgetId)).start();
+ executor.submit(() -> repo.removeSingleNoteWidget(appWidgetId));
}
super.onDeleted(context, appWidgetIds);
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/widget/singlenote/SingleNoteWidgetConfigurationActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/widget/singlenote/SingleNoteWidgetConfigurationActivity.java
index eb06201a..a487e669 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/widget/singlenote/SingleNoteWidgetConfigurationActivity.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/widget/singlenote/SingleNoteWidgetConfigurationActivity.java
@@ -52,7 +52,7 @@ public class SingleNoteWidgetConfigurationActivity extends MainActivity {
int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
- new Thread(() -> {
+ executor.submit(() -> {
try {
mainViewModel.createOrUpdateSingleNoteWidgetData(
new SingleNoteWidgetData(
@@ -71,6 +71,6 @@ public class SingleNoteWidgetConfigurationActivity extends MainActivity {
} catch (SQLException e) {
Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
}
- }).start();
+ });
}
}